File Coverage

blib/lib/MooseX/Aspect.pm
Criterion Covered Total %
statement 21 23 91.3
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 29 31 93.5


line stmt bran cond sub pod time code
1             package MooseX::Aspect;
2              
3 2     2   65587 use 5.010;
  2         7  
  2         80  
4 2     2   13 use strict;
  2         4  
  2         83  
5 2     2   10 use warnings;
  2         8  
  2         62  
6 2     2   10702 use utf8;
  2         8  
  2         14  
7 2     2   2217 use namespace::sweep;
  2         98659  
  2         14  
8              
9             BEGIN {
10 2     2   178 $MooseX::Aspect::AUTHORITY = 'cpan:TOBYINK';
11 2         33 $MooseX::Aspect::VERSION = '0.001';
12             }
13              
14 2     2   20 use Carp;
  2         4  
  2         131  
15 2     2   1076 use Moose ();
  0            
  0            
16             use MooseX::Aspect::Util ();
17             use Moose::Exporter;
18             use MooseX::RoleQR ();
19             use MooseX::Singleton ();
20             use Scalar::Does qw( does -constants );
21              
22             'Moose::Exporter'->setup_import_methods(
23             with_meta => [qw(
24             create_join_point apply_to optionally_apply_to
25             after before around guard whenever has requires with extends
26             )],
27             as_is => [qw(
28             role
29             )],
30             );
31              
32             sub init_meta
33             {
34             shift;
35             'Moose'->init_meta(@_);
36            
37             my %options = @_;
38             Moose::Util::MetaRole::apply_metaroles(
39             for => $options{for_class},
40             class_metaroles => {
41             class => [ 'MooseX::Aspect::Trait::Aspect' ],
42             },
43             );
44             Moose::Util::MetaRole::apply_base_class_roles(
45             for => $options{for_class},
46             roles => [qw(
47             MooseX::Aspect::Trait::Employable
48             MooseX::Singleton::Role::Object
49             )],
50             );
51             }
52              
53             sub create_join_point
54             {
55             my $meta = shift;
56             for (@_)
57             {
58             $meta->add_aspect_join_point(
59             'MooseX::Aspect::Meta::JoinPoint::Adhoc'->new(
60             name => $_,
61             associated_aspect => $meta,
62             ),
63             );
64             }
65             }
66              
67             sub role (&)
68             {
69             return +{ role => +shift };
70             }
71              
72             sub __apply_to
73             {
74             my ($class, $meta, $constraint, $builder) = @_;
75            
76             my $role = $class->create_anon_role(
77             associated_aspect => $meta,
78             application_constraint => $constraint,
79             );
80             $meta->_building_role($role);
81            
82             # Hackity hack!
83             $role->{application_to_class_class} = Moose::Util::with_traits(
84             $role->{application_to_class_class},
85             'MooseX::RoleQR::Trait::Application::ToClass',
86             );
87             $role->{application_to_role_class} = Moose::Util::with_traits(
88             $role->{application_to_role_class},
89             'MooseX::RoleQR::Trait::Application::ToRole',
90             );
91            
92             my $err;
93             eval { $builder->{role}->(); 1 }
94             or $err = $@;
95            
96             $meta->_clear_building_role;
97            
98             die $err if $err;
99             $meta->add_aspect_role($role);
100             }
101              
102             sub apply_to
103             {
104             unshift @_, 'MooseX::Aspect::Meta::Role::Required';
105             goto \&__apply_to;
106             }
107              
108             # Experimental
109             sub optionally_apply_to
110             {
111             unshift @_, 'MooseX::Aspect::Meta::Role::Optional';
112             goto \&__apply_to;
113             }
114              
115             BEGIN {
116             no strict 'refs';
117             foreach my $type (qw(before after around))
118             {
119             *$type = sub
120             {
121             my ($meta, $method, $coderef) = @_;
122             my $sub = 'Moose'->can($type);
123             if (my $r = $meta->_building_role)
124             {
125             @_ = ($r, $method, $coderef);
126             $sub = 'MooseX::RoleQR'->can($type);
127             }
128             goto $sub;
129             }
130             }
131             };
132              
133             sub guard
134             {
135             my ($meta, $method, $coderef) = @_;
136             my $new = sub {
137             my $orig = shift;
138             goto $orig if $coderef->(@_);
139             return;
140             };
141             @_ = ($meta, $method, $new);
142             goto \&around;
143             }
144              
145             sub with
146             {
147             my ($meta, @args) = @_;
148             Moose::Util::apply_all_roles(
149             $meta->_building_role || $meta,
150             @args,
151             );
152             }
153              
154             sub has
155             {
156             my ($meta) = @_;
157             confess "Must not use 'has' inside aspect role"
158             if $meta->_building_role;
159             goto \&Moose::has;
160             }
161              
162             sub extends
163             {
164             my ($meta) = @_;
165             confess "Must not use 'extends' inside aspect role"
166             if $meta->_building_role;
167             goto \&Moose::extends;
168             }
169              
170             sub requires
171             {
172             my ($meta, @args) = @_;
173             my $role = $meta->_building_role
174             or confess "Cannot use 'requires' in aspect outside role";
175             $role->add_required_methods(@args);
176             }
177              
178             sub whenever
179             {
180             my ($meta, $join_point, $coderef) = @_;
181             my $role = $meta->_building_role
182             or confess "Used 'whenever' outside role";
183            
184             if (does $join_point, ARRAY)
185             {
186             whenever($meta, $_, $coderef) for @$join_point;
187             return;
188             }
189             elsif (does $join_point, REGEXP)
190             {
191             whenever($meta, $_, $coderef)
192             for grep { $_->name =~ $join_point } @{ $meta->aspect_join_points };
193             return;
194             }
195            
196             $role->add_whenever_modifier($join_point, $coderef);
197             }
198              
199             BEGIN {
200             package MooseX::Aspect::Trait::Aspect;
201             no thanks;
202             use Moose::Role;
203             use Class::Load qw(load_class);
204             use Moose::Util qw(apply_all_roles);
205             use Carp;
206             use namespace::sweep;
207            
208             has _building_role => (
209             is => 'rw',
210             isa => 'Object',
211             clearer => '_clear_building_role',
212             );
213            
214             has aspect_roles => (
215             traits => [qw/ Array /],
216             is => 'ro',
217             isa => 'ArrayRef[Moose::Meta::Role]',
218             default => sub { [] },
219             handles => {
220             add_aspect_role => 'push',
221             },
222             );
223              
224             has aspect_join_points => (
225             traits => [qw/ Array /],
226             is => 'ro',
227             isa => 'ArrayRef[MooseX::Aspect::Meta::JoinPoint]',
228             default => sub { [] },
229             handles => {
230             add_aspect_join_point => 'push',
231             },
232             );
233            
234             sub find_aspect_join_point
235             {
236             my ($meta, $name) = @_;
237             return $name if blessed $name;
238             my ($r) = grep { $_->name eq $name } @{ $meta->aspect_join_points };
239             $r;
240             }
241            
242             sub setup_aspect_employment
243             {
244             my ($meta, $thing, $required_only) = @_;
245             load_class $thing unless blessed $thing;
246            
247             my @application;
248             foreach my $role (@{ $meta->aspect_roles })
249             {
250             next
251             if $required_only
252             && !$role->DOES('MooseX::Aspect::Meta::Role::Required');
253            
254             push @application, $role
255             if $role->should_apply_to($thing);
256             }
257            
258             apply_all_roles($thing, @application) if @application;
259             Moose::Util::MetaRole::apply_metaroles(
260             for => ref($thing) || $thing,
261             class_metaroles => {
262             class => ['MooseX::Aspect::Trait::Employer'],
263             },
264             );
265             push @{ $thing->meta->employed_aspects }, $meta;
266             }
267            
268             sub run_join_point
269             {
270             my ($meta, $caller, $join_point, $args) = @_;
271             $join_point = $meta->find_aspect_join_point($join_point)
272             or confess "Unknown join_point '$_[2]'";
273            
274             foreach my $role (@{ $meta->aspect_roles })
275             {
276             next unless $caller->does_role($role);
277             foreach my $whenever ($role->get_whenever_modifiers($join_point))
278             {
279             $whenever->execute($args);
280             }
281             }
282            
283             return;
284             }
285             };
286              
287             BEGIN {
288             package MooseX::Aspect::Trait::Employable;
289             use Moose::Role;
290             use MooseX::ClassAttribute;
291            
292             class_has is_setup => (
293             is => 'ro',
294             isa => 'Bool',
295             writer => '_set_is_setup',
296             default => sub { 0 },
297             );
298            
299             sub _auto_setup
300             {
301             my $class = shift;
302             return if $class->is_setup;
303             my $meta = $class->meta;
304             for my $role (@{ $meta->aspect_roles })
305             {
306             next unless $role->DOES('MooseX::Aspect::Meta::Role::Required');
307             Class::Load::load_class( $role->application_constraint );
308             $meta->setup_aspect_employment($role->application_constraint, 1);
309             }
310             $class->_set_is_setup(1);
311             }
312            
313             sub setup
314             {
315             my ($class, @args) = @_;
316             my @isa = $class->meta->linearized_isa;
317             for my $klass (@isa)
318             {
319             next unless $klass->DOES(__PACKAGE__);
320             $klass->_auto_setup;
321             next unless $klass->meta->can('setup_aspect_employment');
322             $klass->meta->setup_aspect_employment($_) for @args;
323             }
324             }
325             };
326              
327             BEGIN {
328             package MooseX::Aspect::Trait::Employer;
329             no thanks;
330             use Moose::Role;
331            
332             has employed_aspects => (
333             is => 'ro',
334             isa => 'ArrayRef[Moose::Meta::Class]',
335             default => sub { [] },
336             );
337            
338             sub employs_aspect
339             {
340             my ($meta, $aspect) = @_;
341             $aspect = $aspect->name if blessed $aspect;
342             return scalar grep { $_->name eq $aspect } @{ $meta->employed_aspects };
343             }
344             };
345              
346              
347             BEGIN {
348             package MooseX::Aspect::Meta::JoinPoint;
349             no thanks;
350             use namespace::sweep;
351             use Moose;
352             has associated_aspect => (is => 'ro', required => 1);
353             };
354              
355             BEGIN {
356             package MooseX::Aspect::Meta::JoinPoint::Adhoc;
357             no thanks;
358             use Moose;
359             use namespace::sweep;
360             extends qw( MooseX::Aspect::Meta::JoinPoint );
361             has name => (is => 'ro', required => 1);
362             };
363              
364             BEGIN {
365             package MooseX::Aspect::Meta::WheneverModifier;
366             no thanks;
367             use Moose;
368             use namespace::sweep;
369            
370             has associated_role => (is => 'ro', required => 1);
371             has join_point => (is => 'ro', required => 1);
372             has code => (is => 'ro', required => 1);
373            
374             sub execute {
375             my ($meta, $args) = @_;
376             $meta->code->( @$args );
377             }
378             };
379              
380             BEGIN {
381             package MooseX::Aspect::Meta::Role;
382             no thanks;
383             use Moose;
384             use Scalar::Does;
385             use namespace::sweep;
386             extends qw( Moose::Meta::Role );
387             with qw( MooseX::RoleQR::Trait::Role );
388            
389             has associated_aspect => (is => 'ro', required => 1);
390             has application_constraint => (
391             is => 'ro',
392             isa => 'Any',
393             );
394             has whenever_modifiers => (
395             is => 'ro',
396             isa => 'HashRef',
397             default => sub { +{} },
398             );
399            
400             sub add_whenever_modifier
401             {
402             my ($meta, $join_point, $code) = @_;
403            
404             $join_point = $meta->associated_aspect->find_aspect_join_point($join_point)
405             unless blessed $join_point;
406            
407             confess "Unknown join_point" unless $join_point;
408            
409             $code = 'MooseX::Aspect::Meta::WheneverModifier'->new(
410             associated_role => $meta,
411             join_point => $join_point,
412             code => $code,
413             ) unless blessed $code;
414            
415             push @{ $meta->whenever_modifiers->{ $join_point->name } }, $code;
416             }
417            
418             sub get_whenever_modifiers
419             {
420             my ($meta, $join_point) = @_;
421             $join_point = $join_point->name if blessed $join_point;
422             @{ $meta->whenever_modifiers->{$join_point} };
423             }
424            
425             sub should_apply_to
426             {
427             my ($meta, $thing) = @_;
428             my $constraint = $meta->application_constraint;
429            
430             return 1 if does($constraint, 'Moose::Meta::TypeConstraint') && $constraint->check($thing);
431             return 1 if does($thing, $constraint);
432             return;
433             }
434             };
435              
436             BEGIN {
437             package MooseX::Aspect::Meta::Role::Required;
438             no thanks;
439             use Moose;
440             extends qw( MooseX::Aspect::Meta::Role );
441             has '+application_constraint' => (isa => 'Str');
442             }
443              
444             BEGIN {
445             package MooseX::Aspect::Meta::Role::Optional;
446             no thanks;
447             use Moose;
448             extends qw( MooseX::Aspect::Meta::Role );
449             }
450              
451             1;
452              
453             __END__
454              
455             =head1 NAME
456              
457             MooseX::Aspect - aspect-oriented programming toolkit for Moose
458              
459             =head1 SYNOPSIS
460              
461             {
462             package User;
463             use Moose;
464             ...;
465             }
466            
467             {
468             package Computer;
469             use Moose;
470             ...;
471             }
472            
473             {
474             package Logging;
475             use MooseX::Aspect;
476            
477             has log_file => (is => 'rw');
478            
479             sub log {
480             $_[0]->log_file->append($_[1]);
481             }
482            
483             apply_to 'User', role {
484             before login => sub {
485             my $self = shift;
486             my $aspect = __PACKAGE__->instance;
487             $aspect->log($self->name . " logged in");
488             };
489             };
490            
491             apply_to 'Computer', role {
492             after connect => sub {
493             my $self = shift;
494             my $aspect = __PACKAGE__->instance;
495             $aspect->log($self->id . " connected to network");
496             };
497             after disconnect => sub {
498             my $self = shift;
499             my $aspect = __PACKAGE__->instance;
500             $aspect->log($self->id . " disconnected from network");
501             };
502             };
503             }
504            
505             Logging->setup; # apply all the aspect's roles to packages
506              
507             =head1 DESCRIPTION
508              
509             Certain parts of code are cross-cutting concerns. A classic example is the
510             one shown in the example: logging. Other cross-cutting concerns include
511             access control, change monitoring (e.g. setting dirty flags) and
512             database transaction management. Aspects help you isolate cross-cutting
513             concerns into modules.
514              
515             In Moose terms, an aspect is a package that defines multiple Moose roles,
516             along with instructions as to what packages the roles should be applied to.
517              
518             =head2 Sugar
519              
520             =over
521              
522             =item C<< apply_to PACKAGE, role { ... }; >>
523              
524             The C<apply_to> and C<role> functions are designed to be used together.
525             They define an anonymous role and specify the package (which may be a
526             class or another role) it is intended to be composed with.
527              
528             The role definition is a more limited version of a standard Moose role
529             definition. In particular, it cannot define methods or attributes;
530             however it can define method modifiers or required methods.
531              
532             =item C<< create_join_point NAME >>
533              
534             Defines a "join point". That is, a hook/event that packages employing
535             this aspect can trigger, and that roles within this aspect can supply
536             code to handle.
537              
538             For example, an aspect called Local::DatabaseIntegrity might define join
539             points called "db-begin" and "db-end" which application code can
540             trigger using:
541              
542             MooseX::Aspect::Util::join_point(
543             'Local::DatabaseIntegrity' => 'db-begin'
544             );
545              
546             Roles within the Local::DatabaseIntegrity aspect can then watch for this
547             join point (using the C<whenever> sugar - see below) and execute code
548             when it is reached. That code might for instance, begin and end database
549             transactions.
550              
551             =item C<< has ATTR => @OPTS >>
552              
553             Standard Moose attribute definition. An aspect is a class (albeit a
554             singleton) so can be instantiated and have attributes.
555              
556             =item C<< extends CLASS >>
557              
558             Standard Moose superclass definition. An aspect is a class so can
559             inherit from other classes. It probably only makes sense to inherit
560             from other aspects.
561              
562             May only be used outside role definitions.
563              
564             =item C<< with ROLE >>
565              
566             Standard Moose role composition.
567              
568             May be used inside or outside role definitions.
569              
570             =item C<< before METHOD => sub { CODE }; >>
571              
572             Standard Moose before modifier. Within roles, uses L<MooseX::RoleQR>
573             which means that the method name can be specified as a regular
574             expression.
575              
576             May be used inside or outside role definitions.
577              
578             =item C<< after METHOD => sub { CODE }; >>
579              
580             Standard Moose after modifier. Within roles, uses L<MooseX::RoleQR>
581             which means that the method name can be specified as a regular
582             expression.
583              
584             May be used inside or outside role definitions.
585              
586             =item C<< around METHOD => sub { CODE }; >>
587              
588             Standard Moose around modifier. Within roles, uses L<MooseX::RoleQR>
589             which means that the method name can be specified as a regular
590             expression.
591              
592             May be used inside or outside role definitions.
593              
594             =item C<< guard METHOD => sub { CODE }; >>
595              
596             Conceptually similar to C<before>, but if the coderef returns false,
597             then the original method is not called, and false is returned instead.
598             See also L<MooseX::Mangle>.
599              
600             May be used inside or outside role definitions.
601              
602             =item C<< whenever JOIN_POINT => sub { CODE }; >>
603              
604             Code that is triggered to run whenever a join point is reached.
605              
606             May only be used inside role definitions.
607              
608             =item C<< requires METHOD >>
609              
610             Standard Moose required method.
611              
612             May only be used inside role definitions.
613              
614             =back
615              
616             =begin undocumented
617              
618             =item C<< init_meta >>
619              
620             Moose internal stuff
621              
622             =item C<< optionally_apply_to PACKAGE, role { ... }; >>
623              
624             Too experiemental to document.
625              
626             =end undocumented
627              
628             =head2 Methods
629              
630             An aspect is a class (albeit a singleton), and thus can define methods.
631             By default it has the following methods:
632              
633             =over
634              
635             =item C<< new >>, C<< instance >>, C<< initialize(%args) >>
636              
637             See L<MooseX::Singleton>.
638              
639             =item C<< setup >>
640              
641             By default, when an aspect is loaded the roles it defines are not actually
642             composed with anything. You need to call the C<setup> class method to compose
643             the roles.
644              
645             =item C<< is_setup >>
646              
647             Class method indicating whether C<setup> has happened.
648              
649             =back
650              
651             =head1 BUGS
652              
653             Please report any bugs to
654             L<http://rt.cpan.org/Dist/Display.html?Queue=MooseX-Aspect>.
655              
656             =head1 SEE ALSO
657              
658             L<Moose>,
659             L<Aspect>.
660              
661             L<MooseX::Aspect::Util>,
662             L<MooseX::Singleton>,
663             L<MooseX::RoleQR>.
664              
665             L<http://en.wikipedia.org/wiki/Aspect-oriented_programming>.
666              
667             =head1 AUTHOR
668              
669             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
670              
671             =head1 COPYRIGHT AND LICENCE
672              
673             This software is copyright (c) 2012 by Toby Inkster.
674              
675             This is free software; you can redistribute it and/or modify it under
676             the same terms as the Perl 5 programming language system itself.
677              
678             =head1 DISCLAIMER OF WARRANTIES
679              
680             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
681             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
682             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
683