File Coverage

blib/lib/Dancer2/Core/Role/Hookable.pm
Criterion Covered Total %
statement 58 59 98.3
branch 19 22 86.3
condition 2 3 66.6
subroutine 13 13 100.0
pod 0 5 0.0
total 92 102 90.2


line stmt bran cond sub pod time code
1             package Dancer2::Core::Role::Hookable;
2             # ABSTRACT: Role for hookable objects
3             $Dancer2::Core::Role::Hookable::VERSION = '2.1.0';
4 199     199   2241776 use Moo::Role;
  199         22559  
  199         1584  
5 199     199   144917 use Dancer2::Core;
  199         593  
  199         7301  
6 199     199   22278 use Dancer2::Core::Types;
  199         530  
  199         2317  
7 199     199   2980601 use Carp 'croak';
  199         508  
  199         17383  
8 199     199   31447 use Safe::Isa;
  199         36329  
  199         33049  
9 199     199   1654 use Sub::Util qw/ subname /;
  199         486  
  199         216710  
10              
11             requires 'supported_hooks', 'hook_aliases';
12              
13             # The hooks registry
14             has hooks => (
15             is => 'ro',
16             isa => HashRef,
17             builder => '_build_hooks',
18             lazy => 1,
19             );
20              
21       867 0   sub BUILD { }
22              
23             # after a hookable object is built, we go over its postponed hooks and register
24             # them if any.
25             after BUILD => sub {
26             my ( $self, $args ) = @_;
27             $self->_add_postponed_hooks($args)
28             if defined $args->{postponed_hooks};
29             };
30              
31             sub _add_postponed_hooks {
32 783     783   2159 my ( $self, $args ) = @_;
33 783         2164 my $postponed_hooks = $args->{postponed_hooks};
34              
35             # find the internal name of the hooks, from the caller name
36 783         2422 my $caller = ref($self);
37 783         8432 my ( $dancer, $h_type, $h_name, @rest ) = map lc, split /::/, $caller;
38 783 50       3588 $h_name = $rest[0] if $h_name eq 'role';
39 783 100       6588 if ( $h_type =~ /(template|logger|serializer|session)/ ) {
40 569         2248 $h_name = $h_type;
41 569         1791 $h_type = 'engine';
42             }
43              
44             # keep only the hooks we want
45 783         3075 $postponed_hooks = $postponed_hooks->{$h_type}{$h_name};
46 783 100       25640 return unless defined $postponed_hooks;
47              
48 5         12 foreach my $name ( keys %{$postponed_hooks} ) {
  5         25  
49 5         15 my $hook = $postponed_hooks->{$name}{hook};
50 5         16 my $caller = $postponed_hooks->{$name}{caller};
51              
52             $self->has_hook($name)
53             or croak "$h_name $h_type does not support the hook `$name'. ("
54 5 50       23 . join( ", ", @{$caller} ) . ")";
  0         0  
55              
56 5         164 $self->add_hook($hook);
57             }
58             }
59              
60             # mst++ for the hint
61             sub _build_hooks {
62 394     394   5375 my ($self) = @_;
63 394         2042 my %hooks = map +( $_ => [] ), $self->supported_hooks;
64 394         8970 return \%hooks;
65             }
66              
67             # This binds a coderef to an installed hook if not already
68             # existing
69             sub add_hook {
70 358     358 0 10957 my ( $self, $hook ) = @_;
71 358         7960 my $name = $hook->name;
72 358         3208 my $code = $hook->code;
73              
74 358 100       1250 croak "Unsupported hook '$name'"
75             unless $self->has_hook($name);
76              
77 357         3853 push @{ $self->hooks->{$name} }, $code;
  357         6881  
78             }
79              
80             # allows the caller to replace the current list of hooks at the given position
81             # this is useful if the object where this role is composed wants to compile the
82             # hooks.
83             sub replace_hook {
84 2028     2028 0 5635 my ( $self, $position, $hooks ) = @_;
85              
86 2028 100       4402 croak "Hook '$position' must be installed first"
87             unless $self->has_hook($position);
88              
89 2027         46658 $self->hooks->{$position} = $hooks;
90             }
91              
92             # Boolean flag to tells if the hook is registered or not
93             sub has_hook {
94 26712     26712 0 53141 my ( $self, $hook_name ) = @_;
95 26712         581064 return exists $self->hooks->{$hook_name};
96             }
97              
98             # Execute the hook at the given position
99             sub execute_hook {
100 22278     22278 0 39723 my $self = shift;
101 22278         35238 my $name = shift;
102              
103 22278 100 66     93062 $name and !ref $name
104             or croak "execute_hook needs a hook name";
105              
106             $name = $self->hook_aliases->{$name}
107 22277 100       62140 if exists $self->hook_aliases->{$name};
108              
109 22277 50       59228 croak "Hook '$name' does not exist"
110             if !$self->has_hook($name);
111              
112 22277 100       225478 $self->$_isa('Dancer2::Core::App') &&
113             $self->log( core => "Entering hook $name" );
114              
115 22277         266485 for my $hook ( @{ $self->hooks->{$name} } ) {
  22277         441104  
116              
117 680 100       6572 $self->log( core => "running hook entry " . subname $hook)
118             if $self->$_isa('Dancer2::Core::App');
119              
120 680         3960 $hook->(@_);
121             }
122             }
123              
124             1;
125              
126             __END__