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