Croak with useful errors when trying to apply meta/base roles to a non-Moose class
[gitmo/Moose.git] / lib / Moose / Util / MetaRole.pm
CommitLineData
231be3be 1package Moose::Util::MetaRole;
2
3use strict;
4use warnings;
114134a5 5use Scalar::Util 'blessed';
231be3be 6
4e5b5d20 7use Carp qw( croak );
231be3be 8use List::MoreUtils qw( all );
f785aad8 9use List::Util qw( first );
3b400403 10use Moose::Deprecated;
4e5b5d20 11use Scalar::Util qw( blessed );
8f05895e 12
231be3be 13sub apply_metaclass_roles {
3b400403 14 Moose::Deprecated::deprecated(
15 feature => 'pre-0.94 MetaRole API',
16 message =>
14bda293 17 'The old Moose::Util::MetaRole API (before version 0.94) has been deprecated.'
18 . ' Using this API will throw an error in Moose 2.0200.'
3b400403 19 );
20
f785aad8 21 goto &apply_metaroles;
22}
23
24sub apply_metaroles {
25 my %args = @_;
26
27 _fixup_old_style_args(\%args);
39e7855c 28
4e5b5d20 29 my $for = _metathing_for( $args{for} );
f785aad8 30
31 if ( $for->isa('Moose::Meta::Role') ) {
32 return _make_new_metaclass( $for, $args{role_metaroles}, 'role' );
33 }
34 else {
35 return _make_new_metaclass( $for, $args{class_metaroles}, 'class' );
231be3be 36 }
f785aad8 37}
38
4e5b5d20 39sub _metathing_for {
40 my $passed = shift;
41
42 my $found
43 = blessed $passed
44 ? $passed
45 : Class::MOP::class_of($passed);
46
47 return $found
48 if defined $found
49 && blessed $found
50 && ( $found->isa('Moose::Meta::Role')
51 || $found->isa('Moose::Meta::Class') );
52
53 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
54
55 my $error_start
56 = 'When using Moose::Util::MetaRole, you must pass a Moose class name,'
57 . ' role name, metaclass object, or metarole object.';
58
59 if ( defined $found && blessed $found ) {
60 croak $error_start
61 . " You passed $passed, and we resolved this to a "
62 . ( blessed $found )
63 . ' object.';
64 }
65
66 if ( defined $passed && !defined $found ) {
67 croak $error_start
68 . " You passed $passed, and this did not resolve to a metaclass or metarole."
69 . ' Maybe you need to call Moose->init_meta to initialize the metaclass first?';
70 }
71
72 if ( !defined $passed ) {
73 croak $error_start
74 . " You passed an undef."
75 . ' Maybe you need to call Moose->init_meta to initialize the metaclass first?';
76 }
77}
78
f785aad8 79sub _fixup_old_style_args {
80 my $args = shift;
81
3b400403 82 return if $args->{class_metaroles} || $args->{role_metaroles};
83
84 Moose::Deprecated::deprecated(
85 feature => 'pre-0.94 MetaRole API',
86 message =>
14bda293 87 'The old Moose::Util::MetaRole API (before version 0.94) has been deprecated.'
88 . ' Using this API will throw an error in Moose 2.0200.'
3b400403 89 );
f785aad8 90
91 $args->{for} = delete $args->{for_class}
92 if exists $args->{for_class};
93
94 my @old_keys = qw(
95 attribute_metaclass_roles
96 method_metaclass_roles
97 wrapped_method_metaclass_roles
98 instance_metaclass_roles
99 constructor_class_roles
100 destructor_class_roles
101 error_class_roles
102
103 application_to_class_class_roles
104 application_to_role_class_roles
105 application_to_instance_class_roles
106 application_role_summation_class_roles
107 );
231be3be 108
f785aad8 109 my $for
110 = blessed $args->{for}
111 ? $args->{for}
112 : Class::MOP::class_of( $args->{for} );
113
114 my $top_key;
115 if ( $for->isa('Moose::Meta::Class') ) {
116 $top_key = 'class_metaroles';
117
118 $args->{class_metaroles}{class} = delete $args->{metaclass_roles}
119 if exists $args->{metaclass_roles};
120 }
121 else {
122 $top_key = 'role_metaroles';
123
124 $args->{role_metaroles}{role} = delete $args->{metaclass_roles}
125 if exists $args->{metaclass_roles};
126 }
127
128 for my $old_key (@old_keys) {
129 my ($new_key) = $old_key =~ /^(.+)_(?:class|metaclass)_roles$/;
130
131 $args->{$top_key}{$new_key} = delete $args->{$old_key}
132 if exists $args->{$old_key};
133 }
134
135 return;
231be3be 136}
137
138sub _make_new_metaclass {
139 my $for = shift;
f785aad8 140 my $roles = shift;
141 my $primary = shift;
142
143 return $for unless keys %{$roles};
231be3be 144
145 my $new_metaclass
f785aad8 146 = exists $roles->{$primary}
147 ? _make_new_class( ref $for, $roles->{$primary} )
148 : blessed $for;
231be3be 149
f785aad8 150 my %classes;
151
152 for my $key ( grep { $_ ne $primary } keys %{$roles} ) {
153 my $attr = first {$_}
154 map { $for->meta->find_attribute_by_name($_) } (
155 $key . '_metaclass',
156 $key . '_class'
157 );
158
159 my $reader = $attr->get_read_method;
160
161 $classes{ $attr->init_arg }
162 = _make_new_class( $for->$reader(), $roles->{$key} );
163 }
164
165 my $new_meta = $new_metaclass->reinitialize( $for, %classes );
166
167 return $new_meta;
231be3be 168}
169
170sub apply_base_class_roles {
f785aad8 171 my %args = @_;
231be3be 172
4e5b5d20 173 my $meta = _metathing_for( $args{for} || $args{for_class} );
174 croak 'You can only apply base class roles to a Moose class, not a role.'
175 if $meta->isa('Moose::Meta::Role');
231be3be 176
177 my $new_base = _make_new_class(
4e5b5d20 178 $meta->name,
f785aad8 179 $args{roles},
231be3be 180 [ $meta->superclasses() ],
181 );
182
183 $meta->superclasses($new_base)
184 if $new_base ne $meta->name();
185}
186
187sub _make_new_class {
188 my $existing_class = shift;
189 my $roles = shift;
190 my $superclasses = shift || [$existing_class];
191
192 return $existing_class unless $roles;
193
8f05895e 194 my $meta = Class::MOP::Class->initialize($existing_class);
231be3be 195
196 return $existing_class
386c056b 197 if $meta->can('does_role') && all { $meta->does_role($_) }
198 grep { !ref $_ } @{$roles};
231be3be 199
200 return Moose::Meta::Class->create_anon_class(
201 superclasses => $superclasses,
202 roles => $roles,
203 cache => 1,
204 )->name();
205}
206
2071;
c59bc009 208
ad46f524 209# ABSTRACT: Apply roles to any metaclass, as well as the object base class
c59bc009 210
ad46f524 211__END__
c59bc009 212
213=head1 SYNOPSIS
214
215 package MyApp::Moose;
216
c59bc009 217 use Moose ();
218 use Moose::Exporter;
49a86a99 219 use Moose::Util::MetaRole;
c59bc009 220
221 use MyApp::Role::Meta::Class;
222 use MyApp::Role::Meta::Method::Constructor;
223 use MyApp::Role::Object;
224
225 Moose::Exporter->setup_import_methods( also => 'Moose' );
226
227 sub init_meta {
228 shift;
f785aad8 229 my %args = @_;
c59bc009 230
f785aad8 231 Moose->init_meta(%args);
c59bc009 232
f785aad8 233 Moose::Util::MetaRole::apply_metaroles(
234 for => $args{for_class},
235 class_metaroles => {
236 class => => ['MyApp::Role::Meta::Class'],
237 constructor => ['MyApp::Role::Meta::Method::Constructor'],
238 },
c59bc009 239 );
240
241 Moose::Util::MetaRole::apply_base_class_roles(
f785aad8 242 for => $args{for_class},
243 roles => ['MyApp::Role::Object'],
c59bc009 244 );
245
f785aad8 246 return $args{for_class}->meta();
c59bc009 247 }
248
249=head1 DESCRIPTION
250
251This utility module is designed to help authors of Moose extensions
252write extensions that are able to cooperate with other Moose
253extensions. To do this, you must write your extensions as roles, which
52a919fe 254can then be dynamically applied to the caller's metaclasses.
c59bc009 255
256This module makes sure to preserve any existing superclasses and roles
257already set for the meta objects, which means that any number of
258extensions can apply roles in any order.
259
260=head1 USAGE
261
110bb412 262The easiest way to use this module is through L<Moose::Exporter>, which can
263generate the appropriate C<init_meta> method for you, and make sure it is
264called when imported.
c59bc009 265
266=head1 FUNCTIONS
267
268This module provides two functions.
269
f785aad8 270=head2 apply_metaroles( ... )
c59bc009 271
8f6b08fd 272This function will apply roles to one or more metaclasses for the specified
273class. It will return a new metaclass object for the class or role passed in
274the "for" parameter.
275
276It accepts the following parameters:
c59bc009 277
278=over 4
279
f785aad8 280=item * for => $name
281
282This specifies the class or for which to alter the meta classes. This can be a
283package name, or an appropriate meta-object (a L<Moose::Meta::Class> or
284L<Moose::Meta::Role>).
c59bc009 285
f785aad8 286=item * class_metaroles => \%roles
c59bc009 287
f785aad8 288This is a hash reference specifying which metaroles will be applied to the
289class metaclass and its contained metaclasses and helper classes.
c59bc009 290
f785aad8 291Each key should in turn point to an array reference of role names.
c59bc009 292
f785aad8 293It accepts the following keys:
c59bc009 294
f785aad8 295=over 8
8286fcd6 296
f785aad8 297=item class
c59bc009 298
f785aad8 299=item attribute
c59bc009 300
f785aad8 301=item method
c59bc009 302
f785aad8 303=item wrapped_method
304
305=item instance
306
307=item constructor
308
309=item destructor
310
311=item error
312
313=back
d401dc20 314
f785aad8 315=item * role_metaroles => \%roles
d401dc20 316
f785aad8 317This is a hash reference specifying which metaroles will be applied to the
318role metaclass and its contained metaclasses and helper classes.
d401dc20 319
f785aad8 320It accepts the following keys:
321
322=over 8
323
324=item role
325
326=item attribute
327
328=item method
329
330=item required_method
331
332=item conflicting_method
333
334=item application_to_class
335
336=item application_to_role
337
338=item application_to_instance
339
340=item application_role_summation
341
342=back
c59bc009 343
344=back
345
f785aad8 346=head2 apply_base_class_roles( for => $class, roles => \@roles )
c59bc009 347
348This function will apply the specified roles to the object's base class.
349
c5fc2c21 350=head1 BUGS
351
352See L<Moose/BUGS> for details on reporting bugs.
353
c59bc009 354=cut