File Coverage

blib/lib/KelpX/Hooks.pm
Criterion Covered Total %
statement 56 56 100.0
branch 12 12 100.0
condition n/a
subroutine 14 14 100.0
pod 1 3 33.3
total 83 85 97.6


line stmt bran cond sub pod time code
1             package KelpX::Hooks;
2             $KelpX::Hooks::VERSION = '1.03';
3 2     2   231598 use v5.10;
  2         6  
4 2     2   8 use strict;
  2         6  
  2         40  
5 2     2   6 use warnings;
  2         3  
  2         71  
6              
7 2     2   16 use Exporter qw(import);
  2         8  
  2         47  
8 2     2   11 use Carp qw(croak);
  2         8  
  2         84  
9 2     2   7 use List::Util qw(any);
  2         2  
  2         233  
10              
11             our @EXPORT = qw(
12             hook
13             );
14              
15             my %hooks;
16             my %hooked;
17              
18             sub apply_hook
19             {
20 13     13 0 36 my ($package, $subname, $decorator) = @_;
21              
22 13         106 my $hooked_method = $package->can($subname);
23 13 100       441 return !!0 unless $hooked_method;
24              
25             {
26 2     2   9 no strict 'refs';
  2         2  
  2         58  
  9         15  
27 2     2   10 no warnings 'redefine';
  2         2  
  2         697  
28              
29 9         45 *{"${package}::$subname"} = sub {
30 10     10   86995 unshift @_, $hooked_method;
31 10         38 goto $decorator;
32 9         60 };
33             }
34              
35 9         46 return !!1;
36             }
37              
38             sub install_hooks
39             {
40 7     7 0 16 my ($package) = @_;
41              
42 7 100       69 return if $hooked{$package}++;
43              
44             my $build_decorator = sub {
45 4     4   8 my $orig = shift;
46 4         9 my @late;
47              
48 4         7 foreach my $hook (@{$hooks{$package}}) {
  4         15  
49 6 100       9 next if apply_hook($package, @{$hook});
  6         19  
50              
51             # not hooked - will try later
52 2         6 push @late, $hook;
53             }
54              
55 4         85 $orig->(@_);
56              
57 4         1533 foreach my $hook (@late) {
58             croak "Trying to hook $hook->[0], which doesn't exist"
59 2 100       4 unless apply_hook($package, @{$hook});
  2         7  
60             }
61 5         23 };
62              
63 5 100       16 croak "Can't install hooks: no build() method in $package"
64             unless apply_hook($package, 'build', $build_decorator);
65             }
66              
67             sub hook
68             {
69 8     8 1 462889 my ($subname, $decorator) = @_;
70 8         21 my $package = caller;
71              
72 8         25 my @forbidden = qw(new build);
73              
74             croak "Hooking $subname() method is forbidden"
75 8 100   16   57 if any { $_ eq $subname } @forbidden;
  16         284  
76              
77 7         34 install_hooks($package);
78              
79 6         9 push @{$hooks{$package}}, [$subname, $decorator];
  6         19  
80              
81 6         26 return;
82             }
83              
84             1;
85              
86             __END__
87              
88             =head1 NAME
89              
90             KelpX::Hooks - Override any method in your Kelp application
91              
92             =head1 SYNOPSIS
93              
94             # in your Kelp application
95             use KelpX::Hooks;
96              
97             # and then...
98             hook "template" => sub {
99             return "No templates for you!";
100             };
101              
102             =head1 DESCRIPTION
103              
104             This module allows you to override methods in your Kelp application class. The
105             provided L</hook> method can be compared to Moose's C<around>, and it mimics
106             its interface. The difference is in how and when the replacement of the actual
107             method occurs.
108              
109             The problem here is that Kelp's modules are modifying the symbol table for the
110             module at the runtime, which makes common attempts to change their methods`
111             behavior futile. You can't override them, you can't change them with method
112             modifiers, you can only replace them with different methods.
113              
114             This module fights the symbol table magic with more symbol table magic. It will
115             replace any method with your anonymous subroutine after the application is
116             built and all the modules have been loaded.
117              
118             =head2 EXPORT
119              
120             =head3 hook
121              
122             hook "sub_name" => sub {
123             my ($original_sub, $self, @arguments) = @_;
124              
125             # your code, preferably do this at some point:
126             return $self->$original_sub(@arguments);
127             };
128              
129             Allows you to provide your own subroutine in place of the one specified. The
130             first argument is the subroutine that's being replaced. It won't be run at all
131             unless you call it explicitly.
132              
133             Please note that Kelp::Less is not supported.
134              
135             =head1 CAVEATS
136              
137             This module works by replacing the C<build> method in symbol tables. Because of
138             this, you cannot hook the build method itself or any method which is run before
139             it. The module will verbosely refuse to hook C<new> or C<build>.
140              
141             Hooks will first try to apply B<before> the build is run. Any modules declared
142             in configuration will have their upgraded functions available inside C<build>.
143             Hooks which fail to apply before C<build> will try again after it finished, and
144             raise an exception if they fail again.
145              
146             =head1 SEE ALSO
147              
148             L<Kelp>, L<Moose::Manual::MethodModifiers>
149              
150             =head1 AUTHOR
151              
152             Bartosz Jarzyna, E<lt>bbrtj.pro@gmail.comE<gt>
153              
154             =head1 COPYRIGHT AND LICENSE
155              
156             Copyright (C) 2020 - 2024 by Bartosz Jarzyna
157              
158             This library is free software; you can redistribute it and/or modify
159             it under the same terms as Perl itself.
160              
161              
162             =cut
163