File Coverage

blib/lib/MooX/BuildArgsHooks.pm
Criterion Covered Total %
statement 48 56 85.7
branch 7 14 50.0
condition 2 6 33.3
subroutine 9 12 75.0
pod 0 3 0.0
total 66 91 72.5


line stmt bran cond sub pod time code
1             package MooX::BuildArgsHooks;
2 5     5   9776 use 5.008001;
  5         19  
3 5     5   29 use strictures 2;
  5         35  
  5         182  
4             our $VERSION = '0.07';
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   2810 use Class::Method::Modifiers qw( install_modifier );
  5         5453  
  5         265  
77 5     5   43 use Moo::Object qw();
  5         8  
  5         86  
78              
79 5     5   23 use Moo::Role;
  5         10  
  5         47  
80 5     5   2077 use namespace::clean;
  5         9049  
  5         58  
81              
82 0         0 BEGIN {
83             package # NO INDEX
84             MooX::BuildArgsHooks::Test;
85 5     5   1371 use Moo;
  5         12  
  5         42  
86             around BUILDARGS => sub{
87 5         5314 my $orig = shift;
88 5         20 my $class = shift;
89 5         30 return $class->$orig( @_ );
90 5     5   2473 };
91 5         1660 has normalize => ( is=>'rw' );
92 5         94626 has transform => ( is=>'rw' );
93 5         1268 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   2728 my $moo = 'Moo::Object';
105              
106             install_modifier(
107             $moo, 'fresh',
108 20         119 'NORMALIZE_BUILDARGS' => sub{ shift; @_ },
  20         55  
109 5 50       89 ) unless $moo->can('NORMALIZE_BUILDARGS');
110              
111             install_modifier(
112             $moo, 'fresh',
113 20         164 'TRANSFORM_BUILDARGS' => sub{ $_[1] },
114 5 50       1010 ) unless $moo->can('TRANSFORM_BUILDARGS');
115              
116             install_modifier(
117             $moo, 'fresh',
118 20         122 'FINALIZE_BUILDARGS' => sub{ $_[1] },
119 5 50       796 ) unless $moo->can('FINALIZE_BUILDARGS');
120              
121 5         753 my $test = MooX::BuildArgsHooks::Test->new();
122 5         167 my $does_normalize = $test->normalize();
123 5         26 my $does_transform = $test->transform();
124 5         14 my $does_finalize = $test->finalize();
125 5         34 $test = undef;
126              
127 5 50 33     39 unless ($does_normalize and $does_transform and $does_finalize) {
      33        
128             install_modifier(
129             $moo, 'around',
130             'BUILDARGS' => sub{
131 20         2778 my ($orig, $class, @args) = @_;
132              
133 20 50       440 @args = $class->NORMALIZE_BUILDARGS( @args ) unless $does_normalize;
134              
135 20         61 my $args = $class->$orig( @args );
136              
137 20 50       527 $args = $class->TRANSFORM_BUILDARGS( { %$args } ) unless $does_transform;
138              
139 20 50       451 $args = $class->FINALIZE_BUILDARGS( { %$args } ) unless $does_finalize;
140              
141 20         189 return $args;
142             },
143 5         34 );
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__