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.0.1';
4 194     194   443090 use Moo::Role;
  194         13034  
  194         1388  
5 194     194   121104 use Dancer2::Core;
  194         1374  
  194         6395  
6 194     194   8175 use Dancer2::Core::Types;
  194         556  
  194         1991  
7 194     194   2792249 use Carp 'croak';
  194         486  
  194         16871  
8 194     194   30706 use Safe::Isa;
  194         33732  
  194         34132  
9 194     194   1636 use Sub::Util qw/ subname /;
  194         522  
  194         215548  
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       843 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 761     761   2299 my ( $self, $args ) = @_;
33 761         2047 my $postponed_hooks = $args->{postponed_hooks};
34              
35             # find the internal name of the hooks, from the caller name
36 761         2256 my $caller = ref($self);
37 761         8067 my ( $dancer, $h_type, $h_name, @rest ) = map lc, split /::/, $caller;
38 761 50       3555 $h_name = $rest[0] if $h_name eq 'role';
39 761 100       6150 if ( $h_type =~ /(template|logger|serializer|session)/ ) {
40 552         3147 $h_name = $h_type;
41 552         2252 $h_type = 'engine';
42             }
43              
44             # keep only the hooks we want
45 761         2748 $postponed_hooks = $postponed_hooks->{$h_type}{$h_name};
46 761 100       21719 return unless defined $postponed_hooks;
47              
48 5         13 foreach my $name ( keys %{$postponed_hooks} ) {
  5         41  
49 5         13 my $hook = $postponed_hooks->{$name}{hook};
50 5         18 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       24 . join( ", ", @{$caller} ) . ")";
  0         0  
55              
56 5         223 $self->add_hook($hook);
57             }
58             }
59              
60             # mst++ for the hint
61             sub _build_hooks {
62 385     385   5313 my ($self) = @_;
63 385         1950 my %hooks = map +( $_ => [] ), $self->supported_hooks;
64 385         8652 return \%hooks;
65             }
66              
67             # This binds a coderef to an installed hook if not already
68             # existing
69             sub add_hook {
70 352     352 0 10424 my ( $self, $hook ) = @_;
71 352         7554 my $name = $hook->name;
72 352         3554 my $code = $hook->code;
73              
74 352 100       1187 croak "Unsupported hook '$name'"
75             unless $self->has_hook($name);
76              
77 351         3847 push @{ $self->hooks->{$name} }, $code;
  351         6647  
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 1992     1992 0 5614 my ( $self, $position, $hooks ) = @_;
85              
86 1992 100       4373 croak "Hook '$position' must be installed first"
87             unless $self->has_hook($position);
88              
89 1991         48588 $self->hooks->{$position} = $hooks;
90             }
91              
92             # Boolean flag to tells if the hook is registered or not
93             sub has_hook {
94 26641     26641 0 57311 my ( $self, $hook_name ) = @_;
95 26641         618782 return exists $self->hooks->{$hook_name};
96             }
97              
98             # Execute the hook at the given position
99             sub execute_hook {
100 22264     22264 0 45461 my $self = shift;
101 22264         40253 my $name = shift;
102              
103 22264 100 66     108344 $name and !ref $name
104             or croak "execute_hook needs a hook name";
105              
106             $name = $self->hook_aliases->{$name}
107 22263 100       75456 if exists $self->hook_aliases->{$name};
108              
109 22263 50       70804 croak "Hook '$name' does not exist"
110             if !$self->has_hook($name);
111              
112 22263 100       253002 $self->$_isa('Dancer2::Core::App') &&
113             $self->log( core => "Entering hook $name" );
114              
115 22263         294488 for my $hook ( @{ $self->hooks->{$name} } ) {
  22263         504637  
116              
117 677 100       6953 $self->log( core => "running hook entry " . subname $hook)
118             if $self->$_isa('Dancer2::Core::App');
119              
120 677         6024 $hook->(@_);
121             }
122             }
123              
124             1;
125              
126             __END__