File Coverage

blib/lib/MooseX/NonMoose/Meta/Role/Class.pm
Criterion Covered Total %
statement 97 99 97.9
branch 37 42 88.1
condition 7 12 58.3
subroutine 21 21 100.0
pod n/a
total 162 174 93.1


line stmt bran cond sub pod time code
1             package MooseX::NonMoose::Meta::Role::Class;
2              
3 24     24   1332626 use Moose::Role;
  24         127587  
  24         167  
4 24     24   151559 use List::Util 1.33 qw(any);
  24         578  
  24         2256  
5 24     24   167 use Module::Runtime qw(use_package_optimistically);
  24         101  
  24         171  
6 24     24   1474 use Try::Tiny;
  24         43  
  24         1825  
7 24     24   137 use List::Util qw( any );
  24         47  
  24         1307  
8 24     24   137 use Scalar::Util 'blessed';
  24         49  
  24         31205  
9              
10             # ABSTRACT: metaclass trait for L<MooseX::NonMoose>
11             our $VERSION = '0.27'; # VERSION
12              
13              
14             has has_nonmoose_constructor => (
15             is => 'rw',
16             isa => 'Bool',
17             default => 0,
18             );
19              
20             has has_nonmoose_destructor => (
21             is => 'rw',
22             isa => 'Bool',
23             default => 0,
24             );
25              
26             # overrides the constructor_name attr that already exists
27             has constructor_name => (
28             is => 'rw',
29             isa => 'Str',
30             lazy => 1,
31             default => sub { shift->throw_error("No constructor name has been set") },
32             );
33              
34             # XXX ugh, really need to fix this in moose
35             around reinitialize => sub {
36             my $orig = shift;
37             my $class = shift;
38             my ($pkg) = @_;
39              
40             my $meta = blessed($pkg) ? $pkg : Moose::Util::find_meta($pkg);
41              
42             $class->$orig(
43             @_,
44             (map { $_->init_arg => $_->get_value($meta) }
45             grep { $_->has_value($meta) }
46             map { $meta->meta->find_attribute_by_name($_) }
47             qw(has_nonmoose_constructor
48             has_nonmoose_destructor
49             constructor_name)),
50             );
51             };
52              
53             sub _determine_constructor_options {
54 26     26   68 my $self = shift;
55 26         192 my @options = @_;
56              
57             # if we're using just the metaclass trait, but not the constructor trait,
58             # then suppress the warning about not inlining a constructor
59 26         1241 my $cc_meta = Moose::Util::find_meta($self->constructor_class);
60 26 100 66     838 return (@options, inline_constructor => 0)
61             unless $cc_meta->can('does_role')
62             && $cc_meta->does_role('MooseX::NonMoose::Meta::Role::Constructor');
63              
64             # do nothing if we explicitly ask for the constructor to not be inlined
65 24         6436 my %options = @options;
66 24 100       192 return @options if !$options{inline_constructor};
67              
68 22         1154 my $constructor_name = $self->constructor_name;
69              
70 22         163 my $local_constructor = $self->get_method($constructor_name);
71 22 100       2648 if (!defined($local_constructor)) {
72 1         7 warn "Not inlining a constructor for " . $self->name . " since "
73             . "its parent " . ($self->superclasses)[0] . " doesn't contain a "
74             . "constructor named '$constructor_name'. "
75             . "If you are certain you don't need to inline your"
76             . " constructor, specify inline_constructor => 0 in your"
77             . " call to " . $self->name . "->meta->make_immutable\n";
78 1         97 return @options;
79             }
80              
81             # do nothing if extends was called, but we then added a method modifier to
82             # the constructor (this will warn, but that's okay)
83             # XXX: this is a fairly big hack, but it should cover most of the cases
84             # that actually show up in practice... it would be nice to do this properly
85             # though
86             return @options
87 21 100       177 if $local_constructor->isa('Class::MOP::Method::Wrapped');
88              
89             # otherwise, explicitly ask for the constructor to be replaced (to suppress
90             # the warning message), since this is the expected usage, and shouldn't
91             # cause a warning
92 20         273 return (replace_constructor => 1, @options);
93             }
94              
95             sub _determine_destructor_options {
96 26     26   76 my $self = shift;
97 26         174 my @options = @_;
98              
99 26         216 return (@options, inline_destructor => 0);
100             }
101              
102             around _immutable_options => sub {
103             my $orig = shift;
104             my $self = shift;
105              
106             my @options = $self->$orig(@_);
107              
108             # do nothing if extends was never called
109             return @options if !$self->has_nonmoose_constructor
110             && !$self->has_nonmoose_destructor;
111              
112             @options = $self->_determine_constructor_options(@options);
113             @options = $self->_determine_destructor_options(@options);
114              
115             return @options;
116             };
117              
118             sub _check_superclass_constructor {
119 36     36   99 my $self = shift;
120              
121             # if the current class defined a custom new method (since subs happen at
122             # BEGIN time), don't try to override it
123 36 100       1917 return if $self->has_method($self->constructor_name);
124              
125             # we need to get the non-moose constructor from the superclass
126             # of the class where this method actually exists, regardless of what class
127             # we're calling it on
128 35         4241 my $super_new = $self->find_next_method_by_name($self->constructor_name);
129              
130             # if we're trying to extend a (non-immutable) moose class, just do nothing
131 35 100       7578 return if $super_new->package_name eq 'Moose::Object';
132              
133 31 50       209 if ($super_new->associated_metaclass->can('constructor_class')) {
134 31         348 my $constructor_class_meta = Class::MOP::Class->initialize(
135             $super_new->associated_metaclass->constructor_class
136             );
137              
138             # if the constructor we're inheriting is already one of ours, there's
139             # no reason to install a new one
140 31 50 33     1068 return if $constructor_class_meta->can('does_role')
141             && $constructor_class_meta->does_role('MooseX::NonMoose::Meta::Role::Constructor');
142              
143             # if the constructor we're inheriting is an inlined version of the
144             # default moose constructor, don't do anything either
145 1     1   33 return if any { $_->isa($constructor_class_meta->name) }
146 31 100       221 $super_new->associated_metaclass->_inlined_methods;
147             }
148              
149             $self->add_method($self->constructor_name => sub {
150 31     31   84714 my $class = shift;
        11      
151              
152 31         311 my $params = $class->BUILDARGS(@_);
153 31 100       742 my @foreign_params = $class->can('FOREIGNBUILDARGS')
154             ? $class->FOREIGNBUILDARGS(@_)
155             : @_;
156 31         218 my $instance = $super_new->execute($class, @foreign_params);
157 31 100       675 if (!blessed($instance)) {
    100          
158 1         5 confess "The constructor for "
159             . $super_new->associated_metaclass->name
160             . " did not return a blessed instance";
161             }
162             elsif (!$instance->isa($class)) {
163 2 100       13 if (!$class->isa(blessed($instance))) {
164 1         5 confess "The constructor for "
165             . $super_new->associated_metaclass->name
166             . " returned an object whose class is not a parent of "
167             . $class;
168             }
169             else {
170 1         3 bless $instance, $class;
171             }
172             }
173 29         184 return Class::MOP::Class->initialize($class)->new_object(
174             __INSTANCE__ => $instance,
175             %$params,
176             );
177 30         2141 });
178 30         3474 $self->has_nonmoose_constructor(1);
179             }
180              
181             sub _check_superclass_destructor {
182 36     36   80 my $self = shift;
183              
184             # if the current class defined a custom DESTROY method (since subs happen
185             # at BEGIN time), don't try to override it
186 36 50       142 return if $self->has_method('DESTROY');
187              
188             # we need to get the non-moose destructor from the superclass
189             # of the class where this method actually exists, regardless of what class
190             # we're calling it on
191 36         1320 my $super_DESTROY = $self->find_next_method_by_name('DESTROY');
192              
193             # if we're trying to extend a (non-immutable) moose class, just do nothing
194 36 100       9066 return if $super_DESTROY->package_name eq 'Moose::Object';
195              
196 4 100 66     19 if ($super_DESTROY->associated_metaclass->can('destructor_class')
197             && $super_DESTROY->associated_metaclass->destructor_class) {
198 1         71 my $destructor_class_meta = Class::MOP::Class->initialize(
199             $super_DESTROY->associated_metaclass->destructor_class
200             );
201              
202             # if the destructor we're inheriting is an inlined version of the
203             # default moose destructor, don't do anything
204 2     2   47 return if any { $_->isa($destructor_class_meta->name) }
205 1 50       66 $super_DESTROY->associated_metaclass->_inlined_methods;
206             }
207              
208             $self->add_method(DESTROY => sub {
209 2     2   634 my $self = shift;
        2      
210              
211 2         11 local $?;
212              
213             Try::Tiny::try {
214 2 50       158 $super_DESTROY->execute($self) if defined $super_DESTROY;
215 2         127 $self->DEMOLISHALL(Devel::GlobalDestruction::in_global_destruction);
216             }
217             Try::Tiny::catch {
218             # Without this, Perl will warn "\t(in cleanup)$@" because of some
219             # bizarre fucked-up logic deep in the internals.
220 24     24   233 no warnings 'misc';
  24         70  
  24         27786  
221 0         0 die $_;
222 2         44 };
223              
224 2         303 return;
225 3         83 });
226 3         383 $self->has_nonmoose_destructor(1);
227             }
228              
229             around superclasses => sub {
230             my $orig = shift;
231             my $self = shift;
232              
233             return $self->$orig unless @_;
234              
235             # XXX lots of duplication between here and MMC::superclasses
236             my ($constructor_name, $constructor_class);
237             for my $super (@{ Data::OptList::mkopt(\@_) }) {
238             my ($name, $opts) = @{ $super };
239              
240             my $cur_constructor_name = delete $opts->{'-constructor_name'};
241              
242             if (defined($constructor_name) && defined($cur_constructor_name)) {
243             $self->throw_error(
244             "You have already specified "
245             . "${constructor_class}::${constructor_name} as the parent "
246             . "constructor; ${name}::${cur_constructor_name} cannot also be "
247             . "the constructor"
248             );
249             }
250              
251             if ($opts && exists($opts->{-version})) {
252             use_package_optimistically($name, $opts->{-version});
253             }
254             else {
255             use_package_optimistically($name);
256             }
257              
258             if (defined($cur_constructor_name)) {
259             my $meta = Moose::Util::find_meta($name);
260             $self->throw_error(
261             "You specified '$cur_constructor_name' as the constructor for "
262             . "$name, but $name has no method by that name"
263             ) unless $meta
264             ? $meta->find_method_by_name($cur_constructor_name)
265             : $name->can($cur_constructor_name);
266             }
267              
268             if (!defined($constructor_name)) {
269             $constructor_name = $cur_constructor_name;
270             $constructor_class = $name;
271             }
272              
273             delete $opts->{'-constructor_name'};
274             }
275              
276             $self->constructor_name(
277             defined($constructor_name) ? $constructor_name : 'new'
278             );
279              
280             my @superclasses = @_;
281             push @superclasses, 'Moose::Object'
282             unless any { !ref($_) && $_->isa('Moose::Object') } @superclasses;
283              
284             my @ret = $self->$orig(@superclasses);
285              
286             $self->_check_superclass_constructor;
287             $self->_check_superclass_destructor;
288              
289             return @ret;
290             };
291              
292             sub _generate_fallback_constructor {
293 26     28   8494 my $self = shift;
294 26         90 my ($class_var) = @_;
295              
296 26         1588 my $new = $self->constructor_name;
297 26         122 my $super_new_class = $self->_find_next_nonmoose_constructor_package;
298 26 100       321 my $arglist = $self->find_method_by_name('FOREIGNBUILDARGS')
299             ? "${class_var}->FOREIGNBUILDARGS(\@_)"
300             : '@_';
301 26         5160 my $instance = "${class_var}->${super_new_class}::$new($arglist)";
302             # XXX: the "my $__DUMMY = " part is because "return do" triggers a weird
303             # bug in pre-5.12 perls (it ends up returning undef)
304 26         430 return '(my $__DUMMY = do { '
305             . 'if (ref($_[0]) eq \'HASH\') { '
306             . '$_[0]->{__INSTANCE__} = ' . $instance . ' '
307             . 'unless exists $_[0]->{__INSTANCE__}; '
308             . '} '
309             . 'else { '
310             . 'unshift @_, __INSTANCE__ => ' . $instance . '; '
311             . '} '
312             . $class_var . '->Moose::Object::new(@_); '
313             . '})';
314             }
315              
316             sub _inline_generate_instance {
317 26     26   6924 my $self = shift;
318 26         95 my ($var, $class_var) = @_;
319              
320 26         1513 my $new = $self->constructor_name;
321 26         128 my $super_new_class = $self->_find_next_nonmoose_constructor_package;
322 26 100       174 my $arglist = $self->find_method_by_name('FOREIGNBUILDARGS')
323             ? "${class_var}->FOREIGNBUILDARGS(\@_)"
324             : '@_';
325 26         4666 my $instance = "${class_var}->${super_new_class}::$new($arglist)";
326             return (
327 26         323 'my ' . $var . ' = ' . $instance . ';',
328             'if (!Scalar::Util::blessed(' . $var . ')) {',
329             $self->_inline_throw_error(
330             '"The constructor for ' . $super_new_class . ' did not return a blessed instance"',
331             ) . ';',
332             '}',
333             'elsif (!' . $var . '->isa(' . $class_var . ')) {',
334             'if (!' . $class_var . '->isa(Scalar::Util::blessed(' . $var . '))) {',
335             $self->_inline_throw_error(
336             '"The constructor for ' . $super_new_class . ' returned an object whose class is not a parent of ' . $class_var . '"',
337             ) . ';',
338             '}',
339             'else {',
340             $self->_inline_rebless_instance($var, $class_var) . ';',
341             '}',
342             '}',
343             );
344             }
345              
346             sub _find_next_nonmoose_constructor_package {
347 52     52   122 my $self = shift;
348 52         2444 my $new = $self->constructor_name;
349 52         320 for my $method (map { $_->{code} } $self->find_all_methods_by_name($new)) {
  144         16559  
350 98 100 66     11875 next if $method->associated_metaclass->meta->can('does_role')
351             && $method->associated_metaclass->meta->does_role('MooseX::NonMoose::Meta::Role::Class');
352 52         1592 return $method->package_name;
353             }
354             # this should never happen (it should find Moose::Object at least)
355 0           $self->throw_error("Couldn't find a non-Moose constructor for " . $self->name);
356             }
357              
358 24     24   210 no Moose::Role;
  24         54  
  24         238  
359              
360             1;
361              
362             __END__
363              
364             =pod
365              
366             =encoding UTF-8
367              
368             =head1 NAME
369              
370             MooseX::NonMoose::Meta::Role::Class - metaclass trait for L<MooseX::NonMoose>
371              
372             =head1 VERSION
373              
374             version 0.27
375              
376             =head1 SYNOPSIS
377              
378             package Foo;
379             use Moose -traits => 'MooseX::NonMoose::Meta::Role::Class';
380              
381             # or
382              
383             package My::Moose;
384             use Moose ();
385             use Moose::Exporter;
386              
387             Moose::Exporter->setup_import_methods;
388             sub init_meta {
389             shift;
390             my %options = @_;
391             Moose->init_meta(%options);
392             Moose::Util::MetaRole::apply_metaclass_roles(
393             for_class => $options{for_class},
394             metaclass_roles => ['MooseX::NonMoose::Meta::Role::Class'],
395             );
396             return Moose::Util::find_meta($options{for_class});
397             }
398              
399             =head1 DESCRIPTION
400              
401             This trait implements everything involved with extending non-Moose classes,
402             other than doing the actual inlining at C<make_immutable> time. See
403             L<MooseX::NonMoose> for more details.
404              
405             =head1 AUTHOR
406              
407             Original author: Jesse Luehrs E<lt>doy@tozt.netE<gt>
408              
409             Current maintainer: Graham Ollis E<lt>plicease@cpan.orgE<gt>
410              
411             =head1 COPYRIGHT AND LICENSE
412              
413             This software is copyright (c) 2009-2025 by Jesse Luehrs.
414              
415             This is free software; you can redistribute it and/or modify it under
416             the same terms as the Perl 5 programming language system itself.
417              
418             =cut