File Coverage

blib/lib/MooX/BuildArgsHooks.pm
Criterion Covered Total %
statement 46 54 85.1
branch 7 14 50.0
condition 2 6 33.3
subroutine 8 11 72.7
pod 0 3 0.0
total 63 88 71.5


line stmt bran cond sub pod time code
1             package MooX::BuildArgsHooks;
2             our $VERSION = '0.08';
3              
4             =encoding utf8
5              
6             =head1 NAME
7              
8             MooX::BuildArgsHooks - Structured BUILDARGS.
9              
10             =head1 SYNOPSIS
11              
12             package Foo;
13             use Moo;
14             with 'MooX::BuildArgsHooks';
15            
16             has bar => (is=>'ro');
17            
18             around NORMALIZE_BUILDARGS => sub{
19             my ($orig, $class, @args) = @_;
20             @args = $class->$orig( @args );
21             return( bar=>$args[0] ) if @args==1 and ref($args[0]) ne 'HASH';
22             return @args;
23             };
24            
25             around TRANSFORM_BUILDARGS => sub{
26             my ($orig, $class, $args) = @_;
27             $args = $class->$orig( $args );
28             $args->{bar} = ($args->{bar}||0) + 10;
29             return $args;
30             };
31            
32             around FINALIZE_BUILDARGS => sub{
33             my ($orig, $class, $args) = @_;
34             $args = $class->$orig( $args );
35             $args->{bar}++;
36             return $args;
37             };
38            
39             print Foo->new( 3 )->bar(); # 14
40              
41             =head1 DESCRIPTION
42              
43             This module installs some hooks directly into L which allow
44             for more fine-grained access to the phases of C. The
45             reason this is important is because if you have various roles and
46             classes modifying BUILDARGS you will often end up with weird
47             behaviors depending on what order the various BUILDARGS wrappers
48             are applied in. By breaking up argument processing into three
49             steps (normalize, transform, and finalize) these conflicts are
50             much less likely to arise.
51              
52             To further avoid these kinds of issues, and this applies to any
53             system where you would C methods from a consuming role or
54             super class not just BUILDARGS, it is recommended that you implement
55             your extensions via methods. This way if something inherits from your
56             role or class they can treat your method as a hook. For example:
57              
58             around TRANSFORM_BUILDARGS => sub{
59             my ($class, $orig, $args) = @_;
60             $args = $class->$orig( $args );
61             return $class->TRANSFORM_FOO_BUILDARGS( $args );
62             };
63            
64             sub TRANSFORM_FOO_BUILDARGS {
65             my ($class, $args) = @_;
66             $args->{bar} = ($args->{bar}||0) + 10;
67             return $args;
68             }
69              
70             Then if some other code wishes to inject code before or after
71             the C class transforming BUILDARGS they can do so at very
72             specific points.
73              
74             =cut
75              
76 5     5   12978 use Class::Method::Modifiers qw( install_modifier );
  5         5303  
  5         302  
77 5     5   35 use Moo::Object qw();
  5         9  
  5         70  
78              
79 5     5   22 use Moo::Role;
  5         10  
  5         34  
80 5     5   1590 use strictures 2;
  5         32  
  5         174  
81 5     5   1324 use namespace::clean;
  5         10950  
  5         26  
82              
83 0         0 BEGIN {
84             package # NO INDEX
85             MooX::BuildArgsHooks::Test;
86 5     5   1343 use Moo;
  5         12  
  5         58  
87             around BUILDARGS => sub{
88 5         4947 my $orig = shift;
89 5         12 my $class = shift;
90 5         21 return $class->$orig( @_ );
91 5     5   2262 };
92 5         1577 has normalize => ( is=>'rw' );
93 5         90541 has transform => ( is=>'rw' );
94 5         1183 has finalize => ( is=>'rw' );
95 0     0 0   sub NORMALIZE_BUILDARGS { $_[0]->normalize(1); shift; @_ }
  0            
  0            
96 0     0 0   sub TRANSFORM_BUILDARGS { $_[0]->transform(1); $_[1] }
  0            
97 0     0 0   sub FINALIZE_BUILDARGS { $_[0]->finalize(1); $_[1] }
  0            
98             }
99              
100             # When installing these modifiers we're going to be super defensive
101             # and not overwrite anything that may have already declared these
102             # methods or even provides this functionality already. This should
103             # hopefully make this module relatively future proof.
104             BEGIN {
105 5     5   2616 my $moo = 'Moo::Object';
106              
107             install_modifier(
108             $moo, 'fresh',
109 20         110 'NORMALIZE_BUILDARGS' => sub{ shift; @_ },
  20         43  
110 5 50       88 ) unless $moo->can('NORMALIZE_BUILDARGS');
111              
112             install_modifier(
113             $moo, 'fresh',
114 20         128 'TRANSFORM_BUILDARGS' => sub{ $_[1] },
115 5 50       1088 ) unless $moo->can('TRANSFORM_BUILDARGS');
116              
117             install_modifier(
118             $moo, 'fresh',
119 20         117 'FINALIZE_BUILDARGS' => sub{ $_[1] },
120 5 50       675 ) unless $moo->can('FINALIZE_BUILDARGS');
121              
122 5         693 my $test = MooX::BuildArgsHooks::Test->new();
123 5         145 my $does_normalize = $test->normalize();
124 5         14 my $does_transform = $test->transform();
125 5         31 my $does_finalize = $test->finalize();
126 5         32 $test = undef;
127              
128 5 50 33     47 unless ($does_normalize and $does_transform and $does_finalize) {
      33        
129             install_modifier(
130             $moo, 'around',
131             'BUILDARGS' => sub{
132 20         2617 my ($orig, $class, @args) = @_;
133              
134 20 50       355 @args = $class->NORMALIZE_BUILDARGS( @args ) unless $does_normalize;
135              
136 20         51 my $args = $class->$orig( @args );
137              
138 20 50       458 $args = $class->TRANSFORM_BUILDARGS( { %$args } ) unless $does_transform;
139              
140 20 50       739 $args = $class->FINALIZE_BUILDARGS( { %$args } ) unless $does_finalize;
141              
142 20         167 return $args;
143             },
144 5         37 );
145             }
146             }
147              
148             # Must declare a custom no-op BUILDARGS otherwise
149             # Method::Generate::Constructor gets in the way.
150             # Alternatively we could modify its inlined BUILDARGS
151             # to include our logic, but that's making things even
152             # more brittle.
153             around BUILDARGS => sub{
154             my $orig = shift;
155             my $class = shift;
156             return $class->$orig( @_ );
157             };
158              
159             1;
160             __END__