make Moose::Util::does_role respect overridden ->does methods
[gitmo/Moose.git] / lib / Moose / Util.pm
CommitLineData
9a641848 1package Moose::Util;
2
9a641848 3use strict;
4use warnings;
5
c01f48b7 6use Data::OptList;
88e88a7b 7use Params::Util qw( _STRING );
7125b244 8use Sub::Exporter;
d7d8a8c7 9use Scalar::Util 'blessed';
d2782813 10use List::Util qw(first);
11use List::MoreUtils qw(any all);
5394a1c7 12use overload ();
094b6ec9 13use Try::Tiny;
38bf2a25 14use Class::MOP;
9a641848 15
7125b244 16my @exports = qw[
d03bd989 17 find_meta
6532ca5a 18 does_role
d03bd989 19 search_class_by_role
b099a649 20 ensure_all_roles
d7d8a8c7 21 apply_all_roles
d26f5671 22 with_traits
ab76842e 23 get_all_init_args
24 get_all_attribute_values
a5e883ae 25 resolve_metatrait_alias
26 resolve_metaclass_alias
5f71050b 27 add_method_modifier
d939e016 28 english_list
27f2f43f 29 meta_attribute_alias
30 meta_class_alias
7125b244 31];
9a641848 32
7125b244 33Sub::Exporter::setup_exporter({
34 exports => \@exports,
11065d1f 35 groups => { all => \@exports }
7125b244 36});
37
38## some utils for the utils ...
39
56ea1a11 40sub find_meta { Class::MOP::class_of(@_) }
9a641848 41
7125b244 42## the functions ...
adf82331 43
7125b244 44sub does_role {
45 my ($class_or_obj, $role) = @_;
adf82331 46
094b6ec9 47 if (try { $class_or_obj->can('does') }) {
48 return $class_or_obj->does($role);
49 }
50
6532ca5a 51 my $meta = find_meta($class_or_obj);
d03bd989 52
7125b244 53 return unless defined $meta;
10a745f5 54 return unless $meta->can('does_role');
7125b244 55 return 1 if $meta->does_role($role);
56 return;
9a641848 57}
58
1631b53f 59sub search_class_by_role {
560c498d 60 my ($class_or_obj, $role) = @_;
d03bd989 61
6532ca5a 62 my $meta = find_meta($class_or_obj);
7125b244 63
64 return unless defined $meta;
65
560c498d 66 my $role_name = blessed $role ? $role->name : $role;
67
7125b244 68 foreach my $class ($meta->class_precedence_list) {
d03bd989 69
70 my $_meta = find_meta($class);
1631b53f 71
7125b244 72 next unless defined $_meta;
73
74 foreach my $role (@{ $_meta->roles || [] }) {
1631b53f 75 return $class if $role->name eq $role_name;
76 }
77 }
78
7125b244 79 return;
1631b53f 80}
81
b099a649 82# this can possibly behave in unexpected ways because the roles being composed
83# before being applied could differ from call to call; I'm not sure if or how
84# to document this possible quirk.
85sub ensure_all_roles {
86 my $applicant = shift;
87 _apply_all_roles($applicant, sub { !does_role($applicant, $_) }, @_);
88}
89
d7d8a8c7 90sub apply_all_roles {
91 my $applicant = shift;
8d4d1cdc 92 _apply_all_roles($applicant, undef, @_);
b099a649 93}
94
95sub _apply_all_roles {
96 my $applicant = shift;
97 my $role_filter = shift;
e606ae5f 98
70ea9161 99 unless (@_) {
100 require Moose;
101 Moose->throw_error("Must specify at least one role to apply to $applicant");
102 }
e606ae5f 103
8e590ae4 104 # If @_ contains role meta objects, mkopt will think that they're values,
105 # because they're references. In other words (roleobj1, roleobj2,
106 # roleobj3) will become [ [ roleobj1, roleobj2 ], [ roleobj3, undef ] ]
107 # -- this is no good. We'll preprocess @_ first to eliminate the potential
108 # bug.
109 # -- rjbs, 2011-04-08
110 my $roles = Data::OptList::mkopt( [@_], {
111 moniker => 'role',
112 name_test => sub {
113 ! ref $_[0] or blessed($_[0]) && $_[0]->isa('Moose::Meta::Role')
114 }
115 });
e606ae5f 116
560c498d 117 my @role_metas;
70ea9161 118 foreach my $role (@$roles) {
560c498d 119 my $meta;
120
121 if ( blessed $role->[0] ) {
122 $meta = $role->[0];
123 }
124 else {
2e7f6cf4 125 Class::MOP::load_class( $role->[0] , $role->[1] );
eea9eb4d 126 $meta = find_meta( $role->[0] );
560c498d 127 }
70ea9161 128
c8d9f1e2 129 unless ($meta && $meta->isa('Moose::Meta::Role') ) {
70ea9161 130 require Moose;
131 Moose->throw_error( "You can only consume roles, "
132 . $role->[0]
133 . " is not a Moose role" );
134 }
560c498d 135
136 push @role_metas, [ $meta, $role->[1] ];
70ea9161 137 }
e606ae5f 138
82a24871 139 if ( defined $role_filter ) {
560c498d 140 @role_metas = grep { local $_ = $_->[0]; $role_filter->() } @role_metas;
8d4d1cdc 141 }
b099a649 142
560c498d 143 return unless @role_metas;
b099a649 144
23360da2 145 Class::MOP::load_class($applicant)
146 unless blessed($applicant)
147 || Class::MOP::class_of($applicant);
148
eea9eb4d 149 my $meta = ( blessed $applicant ? $applicant : Moose::Meta::Class->initialize($applicant) );
6fab0b75 150
560c498d 151 if ( scalar @role_metas == 1 ) {
152 my ( $role, $params ) = @{ $role_metas[0] };
153 $role->apply( $meta, ( defined $params ? %$params : () ) );
d7d8a8c7 154 }
155 else {
560c498d 156 Moose::Meta::Role->combine(@role_metas)->apply($meta);
e606ae5f 157 }
d7d8a8c7 158}
159
d26f5671 160sub with_traits {
161 my ($class, @roles) = @_;
162 return $class unless @roles;
163 return Moose::Meta::Class->create_anon_class(
164 superclasses => [$class],
165 roles => \@roles,
166 cache => 1,
167 )->name;
168}
169
ab76842e 170# instance deconstruction ...
171
172sub get_all_attribute_values {
173 my ($class, $instance) = @_;
174 return +{
175 map { $_->name => $_->get_value($instance) }
176 grep { $_->has_value($instance) }
b2df9268 177 $class->get_all_attributes
ab76842e 178 };
179}
180
181sub get_all_init_args {
182 my ($class, $instance) = @_;
183 return +{
184 map { $_->init_arg => $_->get_value($instance) }
185 grep { $_->has_value($instance) }
d03bd989 186 grep { defined($_->init_arg) }
b2df9268 187 $class->get_all_attributes
ab76842e 188 };
189}
190
50fbbf3d 191sub resolve_metatrait_alias {
50fbbf3d 192 return resolve_metaclass_alias( @_, trait => 1 );
a3738e5b 193}
194
27f2f43f 195sub _build_alias_package_name {
196 my ($type, $name, $trait) = @_;
197 return 'Moose::Meta::'
198 . $type
199 . '::Custom::'
200 . ( $trait ? 'Trait::' : '' )
201 . $name;
202}
203
50fbbf3d 204{
205 my %cache;
a3738e5b 206
50fbbf3d 207 sub resolve_metaclass_alias {
208 my ( $type, $metaclass_name, %options ) = @_;
a3738e5b 209
50fbbf3d 210 my $cache_key = $type . q{ } . ( $options{trait} ? '-Trait' : '' );
211 return $cache{$cache_key}{$metaclass_name}
212 if $cache{$cache_key}{$metaclass_name};
213
27f2f43f 214 my $possible_full_name = _build_alias_package_name(
215 $type, $metaclass_name, $options{trait}
216 );
50fbbf3d 217
218 my $loaded_class = Class::MOP::load_first_existing_class(
219 $possible_full_name,
220 $metaclass_name
221 );
222
223 return $cache{$cache_key}{$metaclass_name}
224 = $loaded_class->can('register_implementation')
225 ? $loaded_class->register_implementation
226 : $loaded_class;
227 }
a3738e5b 228}
229
5f71050b 230sub add_method_modifier {
231 my ( $class_or_obj, $modifier_name, $args ) = @_;
d5447d26 232 my $meta
233 = $class_or_obj->can('add_before_method_modifier')
234 ? $class_or_obj
235 : find_meta($class_or_obj);
5f71050b 236 my $code = pop @{$args};
237 my $add_modifier_method = 'add_' . $modifier_name . '_method_modifier';
238 if ( my $method_modifier_type = ref( @{$args}[0] ) ) {
239 if ( $method_modifier_type eq 'Regexp' ) {
e606ae5f 240 my @all_methods = $meta->get_all_methods;
5f71050b 241 my @matched_methods
e606ae5f 242 = grep { $_->name =~ @{$args}[0] } @all_methods;
243 $meta->$add_modifier_method( $_->name, $code )
5f71050b 244 for @matched_methods;
245 }
775666aa 246 elsif ($method_modifier_type eq 'ARRAY') {
247 $meta->$add_modifier_method( $_, $code ) for @{$args->[0]};
248 }
249 else {
250 $meta->throw_error(
251 sprintf(
252 "Methods passed to %s must be provided as a list, arrayref or regex, not %s",
253 $modifier_name,
254 $method_modifier_type,
255 )
256 );
257 }
5f71050b 258 }
259 else {
260 $meta->$add_modifier_method( $_, $code ) for @{$args};
261 }
262}
d9bb6c63 263
d939e016 264sub english_list {
265 my @items = sort @_;
266
267 return $items[0] if @items == 1;
268 return "$items[0] and $items[1]" if @items == 2;
269
270 my $tail = pop @items;
271 my $list = join ', ', @items;
272 $list .= ', and ' . $tail;
273
274 return $list;
275}
276
833b56a7 277sub _caller_info {
278 my $level = @_ ? ($_[0] + 1) : 2;
279 my %info;
280 @info{qw(package file line)} = caller($level);
281 return \%info;
282}
283
27f2f43f 284sub _create_alias {
285 my ($type, $name, $trait, $for) = @_;
286 my $package = _build_alias_package_name($type, $name, $trait);
287 Class::MOP::Class->initialize($package)->add_method(
288 register_implementation => sub { $for }
289 );
290}
291
292sub meta_attribute_alias {
293 my ($to, $from) = @_;
294 $from ||= caller;
295 my $meta = Class::MOP::class_of($from);
296 my $trait = $meta->isa('Moose::Meta::Role');
297 _create_alias('Attribute', $to, $trait, $from);
298}
299
300sub meta_class_alias {
301 my ($to, $from) = @_;
302 $from ||= caller;
303 my $meta = Class::MOP::class_of($from);
304 my $trait = $meta->isa('Moose::Meta::Role');
305 _create_alias('Class', $to, $trait, $from);
306}
307
88e88a7b 308# XXX - this should be added to Params::Util
5394a1c7 309sub _STRINGLIKE0 ($) {
88e88a7b 310 return _STRING( $_[0] )
5394a1c7 311 || ( defined $_[0]
312 && $_[0] eq q{} )
88e88a7b 313 || ( blessed $_[0]
314 && overload::Method( $_[0], q{""} )
315 && length "$_[0]" );
316}
317
61907a02 318sub _reconcile_roles_for_metaclass {
319 my ($class_meta_name, $super_meta_name) = @_;
320
321 my @role_differences = _role_differences(
322 $class_meta_name, $super_meta_name,
323 );
324
325 # handle the case where we need to fix compatibility between a class and
326 # its parent, but all roles in the class are already also done by the
327 # parent
2c739d1a 328 # see t/metaclasses/metaclass_compat_no_fixing_bug.t
61907a02 329 return $super_meta_name
330 unless @role_differences;
331
332 return Moose::Meta::Class->create_anon_class(
333 superclasses => [$super_meta_name],
334 roles => [map { $_->name } @role_differences],
335 cache => 1,
336 )->name;
337}
338
339sub _role_differences {
340 my ($class_meta_name, $super_meta_name) = @_;
6e15f532 341 my @super_role_metas
342 = grep { !$_->isa('Moose::Meta::Role::Composite') }
343 $super_meta_name->meta->can('calculate_all_roles_with_inheritance')
344 ? $super_meta_name->meta->calculate_all_roles_with_inheritance
345 : $super_meta_name->meta->can('calculate_all_roles')
346 ? $super_meta_name->meta->calculate_all_roles
347 : ();
348 my @role_metas
349 = grep { !$_->isa('Moose::Meta::Role::Composite') }
350 $class_meta_name->meta->can('calculate_all_roles_with_inheritance')
351 ? $class_meta_name->meta->calculate_all_roles_with_inheritance
352 : $class_meta_name->meta->can('calculate_all_roles')
353 ? $class_meta_name->meta->calculate_all_roles
354 : ();
61907a02 355 my @differences;
356 for my $role_meta (@role_metas) {
357 push @differences, $role_meta
358 unless any { $_->name eq $role_meta->name } @super_role_metas;
359 }
360 return @differences;
361}
362
d2782813 363sub _classes_differ_by_roles_only {
364 my ( $self_meta_name, $super_meta_name ) = @_;
365
366 my $common_base_name
367 = _find_common_base( $self_meta_name, $super_meta_name );
368
369 return unless defined $common_base_name;
370
371 my @super_meta_name_ancestor_names
372 = _get_ancestors_until( $super_meta_name, $common_base_name );
373 my @class_meta_name_ancestor_names
374 = _get_ancestors_until( $self_meta_name, $common_base_name );
375
376 return
377 unless all { _is_role_only_subclass($_) }
378 @super_meta_name_ancestor_names,
379 @class_meta_name_ancestor_names;
380
381 return 1;
382}
383
384sub _find_common_base {
385 my ($meta1, $meta2) = map { Class::MOP::class_of($_) } @_;
386 return unless defined $meta1 && defined $meta2;
387
388 # FIXME? This doesn't account for multiple inheritance (not sure
389 # if it needs to though). For example, if somewhere in $meta1's
390 # history it inherits from both ClassA and ClassB, and $meta2
391 # inherits from ClassB & ClassA, does it matter? And what crazy
392 # fool would do that anyway?
393
394 my %meta1_parents = map { $_ => 1 } $meta1->linearized_isa;
395
396 return first { $meta1_parents{$_} } $meta2->linearized_isa;
397}
398
399sub _get_ancestors_until {
400 my ($start_name, $until_name) = @_;
401
402 my @ancestor_names;
403 for my $ancestor_name (Class::MOP::class_of($start_name)->linearized_isa) {
404 last if $ancestor_name eq $until_name;
405 push @ancestor_names, $ancestor_name;
406 }
407 return @ancestor_names;
408}
409
410sub _is_role_only_subclass {
411 my ($meta_name) = @_;
412 my $meta = Class::MOP::Class->initialize($meta_name);
413 my @parent_names = $meta->superclasses;
414
415 # XXX: don't feel like messing with multiple inheritance here... what would
416 # that even do?
417 return unless @parent_names == 1;
418 my ($parent_name) = @parent_names;
419 my $parent_meta = Class::MOP::Class->initialize($parent_name);
420
421 # only get the roles attached to this particular class, don't look at
422 # superclasses
423 my @roles = $meta->can('calculate_all_roles')
424 ? $meta->calculate_all_roles
425 : ();
426
427 # it's obviously not a role-only subclass if it doesn't do any roles
428 return unless @roles;
429
430 # loop over all methods that are a part of the current class
431 # (not inherited)
432 for my $method ( $meta->_get_local_methods ) {
433 # always ignore meta
ba7d613d 434 next if $method->isa('Class::MOP::Method::Meta');
d2782813 435 # we'll deal with attributes below
436 next if $method->can('associated_attribute');
437 # if the method comes from a role we consumed, ignore it
438 next if $meta->can('does_role')
439 && $meta->does_role($method->original_package_name);
440 # FIXME - this really isn't right. Just because a modifier is
441 # defined in a role doesn't mean it isn't _also_ defined in the
442 # subclass.
443 next if $method->isa('Class::MOP::Method::Wrapped')
444 && (
445 (!scalar($method->around_modifiers)
446 || any { $_->has_around_method_modifiers($method->name) } @roles)
447 && (!scalar($method->before_modifiers)
448 || any { $_->has_before_method_modifiers($method->name) } @roles)
449 && (!scalar($method->after_modifiers)
450 || any { $_->has_after_method_modifiers($method->name) } @roles)
451 );
452
453 return 0;
454 }
455
456 # loop over all attributes that are a part of the current class
457 # (not inherited)
458 # FIXME - this really isn't right. Just because an attribute is
459 # defined in a role doesn't mean it isn't _also_ defined in the
460 # subclass.
461 for my $attr (map { $meta->get_attribute($_) } $meta->get_attribute_list) {
462 next if any { $_->has_attribute($attr->name) } @roles;
463
464 return 0;
465 }
466
467 return 1;
468}
469
9a641848 4701;
471
ad46f524 472# ABSTRACT: Utilities for working with Moose classes
473
9a641848 474__END__
475
476=pod
477
9a641848 478=head1 SYNOPSIS
479
6532ca5a 480 use Moose::Util qw/find_meta does_role search_class_by_role/;
481
482 my $meta = find_meta($object) || die "No metaclass found";
9a641848 483
adf82331 484 if (does_role($object, $role)) {
485 print "The object can do $role!\n";
9a641848 486 }
487
1631b53f 488 my $class = search_class_by_role($object, 'FooRole');
489 print "Nearest class with 'FooRole' is $class\n";
490
7125b244 491=head1 DESCRIPTION
492
2c3bf4e7 493This module provides a set of utility functions. Many of these
494functions are intended for use in Moose itself or MooseX modules, but
495some of them may be useful for use in your own code.
7125b244 496
497=head1 EXPORTED FUNCTIONS
9a641848 498
499=over 4
500
2c3bf4e7 501=item B<find_meta($class_or_obj)>
502
503This method takes a class name or object and attempts to find a
3ff98e47 504metaclass for the class, if one exists. It will B<not> create one if it
2c3bf4e7 505does not yet exist.
506
560c498d 507=item B<does_role($class_or_obj, $role_or_obj)>
2c3bf4e7 508
560c498d 509Returns true if C<$class_or_obj> does the given C<$role_or_obj>. The role can
510be provided as a name or a L<Moose::Meta::Role> object.
6532ca5a 511
560c498d 512The class must already have a metaclass for this to work. If it doesn't, this
513function simply returns false.
6532ca5a 514
560c498d 515=item B<search_class_by_role($class_or_obj, $role_or_obj)>
7125b244 516
2c3bf4e7 517Returns the first class in the class's precedence list that does
560c498d 518C<$role_or_obj>, if any. The role can be either a name or a
519L<Moose::Meta::Role> object.
7125b244 520
2c3bf4e7 521The class must already have a metaclass for this to work.
7125b244 522
2c3bf4e7 523=item B<apply_all_roles($applicant, @roles)>
7125b244 524
2c3bf4e7 525This function applies one or more roles to the given C<$applicant> The
526applicant can be a role name, class name, or object.
d7d8a8c7 527
2c3bf4e7 528The C<$applicant> must already have a metaclass object.
529
560c498d 530The list of C<@roles> should a list of names or L<Moose::Meta::Role> objects,
531each of which can be followed by an optional hash reference of options
532(C<-excludes> and C<-alias>).
d7d8a8c7 533
b099a649 534=item B<ensure_all_roles($applicant, @roles)>
535
536This function is similar to L</apply_all_roles>, but only applies roles that
537C<$applicant> does not already consume.
538
d26f5671 539=item B<with_traits($class_name, @role_names)>
540
541This function creates a new class from C<$class_name> with each of
542C<@role_names> applied. It returns the name of the new class.
543
ab76842e 544=item B<get_all_attribute_values($meta, $instance)>
545
2c3bf4e7 546Returns a hash reference containing all of the C<$instance>'s
547attributes. The keys are attribute names.
ab76842e 548
549=item B<get_all_init_args($meta, $instance)>
550
2c3bf4e7 551Returns a hash reference containing all of the C<init_arg> values for
552the instance's attributes. The values are the associated attribute
553values. If an attribute does not have a defined C<init_arg>, it is
554skipped.
555
556This could be useful in cloning an object.
ab76842e 557
a3738e5b 558=item B<resolve_metaclass_alias($category, $name, %options)>
559
560=item B<resolve_metatrait_alias($category, $name, %options)>
561
2c3bf4e7 562Resolves a short name to a full class name. Short names are often used
563when specifying the C<metaclass> or C<traits> option for an attribute:
a3738e5b 564
565 has foo => (
566 metaclass => "Bar",
567 );
568
8a8856de 569The name resolution mechanism is covered in
570L<Moose/Metaclass and Trait Name Resolution>.
5f71050b 571
27f2f43f 572=item B<meta_class_alias($to[, $from])>
573
574=item B<meta_attribute_alias($to[, $from])>
575
576Create an alias from the class C<$from> (or the current package, if
577C<$from> is unspecified), so that
578L<Moose/Metaclass and Trait Name Resolution> works properly.
579
57385c0d 580=item B<english_list(@items)>
581
582Given a list of scalars, turns them into a proper list in English
583("one and two", "one, two, three, and four"). This is used to help us
584make nicer error messages.
585
7125b244 586=back
9a641848 587
7125b244 588=head1 TODO
9a641848 589
7125b244 590Here is a list of possible functions to write
9a641848 591
7125b244 592=over 4
1631b53f 593
7125b244 594=item discovering original method from modified method
1631b53f 595
7125b244 596=item search for origin class of a method or attribute
1631b53f 597
9a641848 598=back
599
600=head1 BUGS
601
d4048ef3 602See L<Moose/BUGS> for details on reporting bugs.
9a641848 603
9a641848 604=cut
605