File Coverage

blib/lib/MooX/Role/Parameterized.pm
Criterion Covered Total %
statement 87 93 93.5
branch 27 34 79.4
condition 14 21 66.6
subroutine 16 17 94.1
pod 5 6 83.3
total 149 171 87.1


line stmt bran cond sub pod time code
1             package MooX::Role::Parameterized;
2              
3 13     13   1182036 use strict;
  13         32  
  13         490  
4 13     13   65 use warnings;
  13         26  
  13         950  
5              
6             # ABSTRACT: MooX::Role::Parameterized - roles with composition parameters
7              
8 13     13   6790 use Module::Runtime qw(use_module);
  13         28277  
  13         88  
9 13     13   946 use Carp qw(carp croak);
  13         35  
  13         899  
10 13     13   81 use Exporter qw(import);
  13         28  
  13         398  
11 13     13   4356 use Moo::Role qw();
  13         90697  
  13         326  
12 13     13   6287 use MooX::BuildClass;
  13         150920  
  13         102  
13 13     13   6963 use MooX::Role::Parameterized::Mop;
  13         49  
  13         21097  
14              
15             our $VERSION = "0.501";
16              
17             our @EXPORT = qw(parameter role apply apply_roles_to_target);
18              
19             our $VERBOSE = 0;
20              
21             our %INFO;
22              
23             sub apply {
24 0 0   0 1 0 carp "apply method is deprecated, please use 'apply_roles_to_target'"
25             if $VERBOSE;
26              
27 0         0 goto &apply_roles_to_target;
28             }
29              
30             sub apply_roles_to_target {
31 26     26 1 843978 my ( $role, $args, %extra ) = @_;
32              
33 26 50       129 croak
34             "unable to apply parameterized role: not an MooX::Role::Parameterized"
35             if !__PACKAGE__->is_role($role);
36              
37 26 100       166 $args = [$args] if ref($args) ne ref( [] );
38              
39 26 100       131 my $target = defined( $extra{target} ) ? $extra{target} : (caller)[0];
40              
41 26 100 66     452 if ( exists $INFO{$role}
      66        
42             && exists $INFO{$role}{code_for}
43             && ref $INFO{$role}{code_for} eq "CODE" )
44             {
45 25         198 my $mop = MooX::Role::Parameterized::Mop->new(
46             target => $target,
47             role => $role
48             );
49              
50 25         87 my $parameter_definition_klass =
51             _fetch_parameter_definition_klass($role);
52              
53 25         46 foreach my $params ( @{$args} ) {
  25         64  
54 27 100       84 if ( defined $parameter_definition_klass ) {
55 5         12 eval { $params = $parameter_definition_klass->new($params); };
  5         87  
56              
57 5 100       5079 croak(
58             "unable to apply parameterized role '${role}' to '${target}': $@"
59             ) if $@;
60             }
61              
62 26         165 $INFO{$role}{code_for}->( $params, $mop );
63             }
64             }
65              
66 25         7260 Moo::Role->apply_roles_to_package( $target, $role );
67             }
68              
69             sub role(&) { ##no critic (Subroutines::ProhibitSubroutinePrototypes)
70 14     14 1 265274 my $package = (caller)[0];
71              
72 14   100     269 $INFO{$package} ||= { is_role => 1 };
73              
74             croak "role subroutine called multiple times on '$package'"
75 14 50       94 if exists $INFO{$package}{code_for};
76              
77 14         72 $INFO{$package}{code_for} = shift;
78             }
79              
80             sub parameter {
81 5     5 1 690710 my $package = (caller)[0];
82              
83 5   50     56 $INFO{$package} ||= { is_role => 1 };
84              
85 5   50     10 push @{ $INFO{$package}{parameters_definition} ||= [] }, \@_;
  5         42  
86             }
87              
88             sub is_role {
89 47     47 1 8453 my ( $klass, $role ) = @_;
90              
91 47   66     451 return !!( $INFO{$role} && $INFO{$role}->{is_role} );
92             }
93              
94             sub build_apply_roles_to_package {
95 14     14 0 35 my ( $klass, $orig ) = @_;
96              
97             return sub {
98 15     15   548572 my $target = (caller)[0];
99              
100 15         222 while (@_) {
101 19         59 my $role = shift;
102              
103 19         39 eval { use_module($role) };
  19         100  
104              
105 19 100       25601 if ( MooX::Role::Parameterized->is_role($role) ) {
106 16         48 my $params = [ {} ];
107              
108 16 100 66     88 if ( @_ && ref $_[0] ) {
109 13         39 $params = shift;
110              
111 13 100       59 $params = [$params] if ref($params) ne ref( [] );
112             }
113              
114 16         34 foreach my $args ( @{$params} ) {
  16         45  
115 17         765 $role->apply_roles_to_target( $args, target => $target );
116             }
117              
118 15         25751 next;
119             }
120              
121 3 100 66     52 if ( defined $orig && ref $orig eq 'CODE' ) {
122 2         10 $orig->($role);
123              
124 2         5643 next;
125             }
126              
127 1 50       9 if ( Moo::Role->is_role($role) ) {
128 0         0 Moo::Role->apply_roles_to_package( $target, $role );
129 0         0 eval {
130 0         0 Moo::Role->_maybe_reset_handlemoose($target); ##no critic(Subroutines::ProtectPrivateSubs)
131             };
132              
133 0         0 next;
134             }
135              
136 1         66 croak "Can't apply role to '${target}' - '${role}' is neither a "
137             . "MooX::Role::Parameterized, Moo::Role or Role::Tiny role";
138             }
139 14         119 };
140             }
141              
142              
143             sub _fetch_parameter_definition_klass {
144 25     25   64 my $role = shift;
145              
146 25 50       102 return if !exists $INFO{$role};
147              
148 25 100       85 if ( !exists $INFO{$role}{parameter_definition_klass} ) {
149 23 100       91 return if !exists $INFO{$role}{parameters_definition};
150              
151 3         24 my $parameters_definition = $INFO{$role}{parameters_definition};
152              
153             $INFO{$role}{parameter_definition_klass} =
154 3         12 _create_parameters_klass( $role, $parameters_definition );
155              
156 3         16 delete $INFO{$role}{parameters_definition};
157             }
158              
159 5         19 return $INFO{$role}{parameter_definition_klass};
160             }
161              
162             sub _create_parameters_klass {
163 3     3   11 my ( $package, $parameters_definition ) = @_;
164              
165 3         8 my $klass = "${package}::__MOOX_ROLE_PARAMETERIZED_PARAMS__";
166              
167 3 50       42 return $klass if $klass->isa("Moo::Object");
168              
169 3         12 my @klass_definition = ( extends => "Moo::Object" );
170              
171 3         8 foreach my $parameter_definition ( @{$parameters_definition} ) {
  3         10  
172 3         36 push @klass_definition, has => $parameter_definition;
173             }
174              
175 3         26 BuildClass $klass => @klass_definition;
176              
177 3         69435 return $klass;
178             }
179              
180             1;
181             __END__
182              
183             =head1 NAME
184              
185             MooX::Role::Parameterized - roles with composition parameters
186              
187             =head1 SYNOPSIS
188              
189             package Counter;
190             use Moo::Role;
191             use MooX::Role::Parameterized;
192             use Types::Standard qw( Str );
193              
194             parameter name => (
195             is => 'ro', # this is mandatory on Moo
196             isa => Str, # optional type
197             required => 1, # mark the parameter "name" as "required"
198             );
199              
200             role {
201             my ( $p, $mop ) = @_;
202              
203             my $name = $p->name; # $p->{name} will also work
204            
205             $mop->has($name => (
206             is => 'rw',
207             default => sub { 0 },
208             ));
209            
210             $mop->method("increment_$name" => sub {
211             my $self = shift;
212             $self->$name($self->$name + 1);
213             });
214            
215             $mop->method("reset_$name" => sub {
216             my $self = shift;
217             $self->$name(0);
218             });
219             };
220            
221             package MyGame::Weapon;
222             use Moo;
223             use MooX::Role::Parameterized::With;
224            
225             with Counter => { # injects 'enchantment' attribute and
226             name => 'enchantment', # methods increment_enchantment ( +1 )
227             }; # reset_enchantment (set to zero)
228            
229             package MyGame::Wand;
230             use Moo;
231             use MooX::Role::Parameterized::With;
232              
233             with Counter => { # injects 'zapped' attribute and
234             name => 'zapped', # methods increment_zapped ( +1 )
235             }; # reset_zapped (set to zero)
236              
237             =head1 DESCRIPTION
238              
239             It is an B<experimental> port of L<MooseX::Role::Parameterized> to L<Moo>.
240              
241             =head1 FUNCTIONS
242              
243             This package exports the following subroutines: C<parameter>, C<role>, C<apply_roles_to_target> and C<apply>.
244              
245             =head2 parameter
246              
247             This function receive the same parameter as C<Moo::has>. If present, the parameter hash reference will be blessed as a Moo class. This is useful to add default values or set some parameters as required.
248              
249             =head2 role
250              
251             This function accepts just B<one> code block. Will execute this code then we apply the Role in the
252             target classand will receive the parameter hash reference + one B<mop> object.
253              
254             The B<params> reference will be blessed if there is some parameter defined on this role.
255              
256             The B<mop> object is a proxy to the target class.
257              
258             It offer a better way to call C<has>, C<after>, C<before>, C<around>, C<with> and C<requires> without side effects.
259              
260             Use C<method> to inject a new method and C<meta> to access TARGET_PACKAGE->meta
261              
262             Please use:
263              
264             my ($params, $mop) = @_;
265             ...
266             $mop->has($params->{attribute} =>(...));
267              
268             $mop->method(name => sub { ... });
269              
270             $mop->meta->make_immutable;
271              
272             =head2 apply
273              
274             Alias to C<apply_roles_to_target>
275              
276             =head2 apply_roles_to_target
277              
278             When called, will apply the C</role> on the current package. The behavior depends of the parameter list.
279              
280             This will install the role in the target package. Does not need call C<with>.
281              
282             Important, if you want to apply the role multiple times, like to create multiple attributes, please pass an B<arrayref>.
283              
284             package FooWith;
285              
286             use Moo;
287             use MooX::Role::Parameterized::With; # overrides Moo::with
288              
289             with "Bar" => { # apply parameterized role Bar once
290             attr => 'baz',
291             method => 'run'
292             }, "Other::Role" => [ # apply parameterized role "Other::Role" twice
293             { ... }, # with different parameters
294             { ... },
295             ],
296             "Other::Role" => { ...}, # apply it again
297             "Some::Moo::Role",
298             "Some::Role::Tiny";
299              
300             has foo => ( is => 'ro'); # continue with normal Moo code
301              
302             =head1 STATIC METHOS
303              
304             =head2 is_role
305              
306             Returns true if the package is a L<MooX::Role::Parameterized>.
307              
308             MooX::Role::Parameterized->is_role("My::Role");
309              
310             =head1 DEPRECATED FUNCTIONS
311              
312             =head2 hasp
313              
314             Removed
315              
316             =head2 method
317              
318             Removed
319              
320             =head1 VARIABLES
321              
322             =head2 MooX::Role::Parameterized::VERBOSE
323              
324             By setting C<$MooX::Role::Parameterized::VERBOSE> with some true value we will carp on certain conditions
325             (method override, unable to load package, etc).
326              
327             Default is false.
328              
329             =head1 MooX::Role::Parameterized::With
330              
331             See L<MooX::Role::Parameterized::With> package to easily load and apply roles.
332              
333             Allow to do this:
334              
335             package FooWith;
336              
337             use Moo;
338             use MooX::Role::Parameterized::With; # overrides Moo::with
339              
340             with "Bar" => { # apply parameterized role Bar once
341             attr => 'baz',
342             method => 'run'
343             }, "Other::Role" => [ # apply parameterized role "Other::Role" twice
344             { ... }, # with different parameters
345             { ... },
346             ],
347             "Some::Moo::Role",
348             "Some::Role::Tiny";
349              
350             has foo => ( is => 'ro'); # continue with normal Moo code
351              
352             =head1 SEE ALSO
353              
354             L<MooseX::Role::Parameterized> - Moose version
355              
356             =head1 THANKS
357              
358             =over
359              
360             =item *
361              
362             FGA <fabrice.gabolde@gmail.com>
363              
364             =item *
365              
366             PERLANCAR <perlancar@gmail.com>
367              
368             =item *
369              
370             CHOROBA <choroba@cpan.org>
371              
372             =item *
373              
374             Ed J <mohawk2@users.noreply.github.com>
375              
376             =back
377              
378             =head1 LICENSE
379              
380             The MIT License
381            
382             Permission is hereby granted, free of charge, to any person
383             obtaining a copy of this software and associated
384             documentation files (the "Software"), to deal in the Software
385             without restriction, including without limitation the rights to
386             use, copy, modify, merge, publish, distribute, sublicense,
387             and/or sell copies of the Software, and to permit persons to
388             whom the Software is furnished to do so, subject to the
389             following conditions:
390            
391             The above copyright notice and this permission notice shall
392             be included in all copies or substantial portions of the
393             Software.
394            
395             THE SOFTWARE IS PROVIDED "AS IS", WITHOUT
396             WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
397             INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
398             MERCHANTABILITY, FITNESS FOR A PARTICULAR
399             PURPOSE AND NONINFRINGEMENT. IN NO EVENT
400             SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
401             LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
402             LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
403             TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
404             CONNECTION WITH THE SOFTWARE OR THE USE OR
405             OTHER DEALINGS IN THE SOFTWARE.
406              
407             =head1 AUTHOR
408              
409             Tiago Peczenyj <tiago (dot) peczenyj (at) gmail (dot) com>
410              
411             =head1 BUGS
412              
413             Please report any bugs or feature requests on the bugtracker website