File Coverage

blib/lib/POE/Declare.pm
Criterion Covered Total %
statement 107 125 85.6
branch 16 34 47.0
condition n/a
subroutine 23 24 95.8
pod 2 4 50.0
total 148 187 79.1


line stmt bran cond sub pod time code
1             package POE::Declare;
2              
3             =pod
4              
5             =head1 NAME
6              
7             POE::Declare - A POE abstraction layer for conciseness and simplicity
8              
9             =head1 SYNOPSIS
10              
11             package MyComponent;
12            
13             use strict;
14             use POE::Declare {
15             foo => 'Attribute',
16             bar => 'Internal',
17             Username => 'Param',
18             Password => 'Param',
19             };
20            
21             declare TimeoutError => 'Message';
22            
23             sub hello : Event {
24             print "Hello World!\n";
25             $_[SELF]->hello_timeout_start;
26             }
27            
28             sub hello_timeout : Timeout(30) {
29             print "Alas, I die!\n";
30            
31             # Tell our parent as well
32             $_[SELF]->TimeoutError;
33             }
34            
35             compile;
36              
37             =head1 DESCRIPTION
38              
39             L is a very powerful and flexible system for doing asynchronous
40             programming. But it has the reputation of being difficult to learn,
41             with a somewhat confusing set of abstractions.
42              
43             In particular, it can be tricky to resolve L's way of programming
44             with the highly abstracted OO structures that many people are used to,
45             with layer stacked upon layer ad-infinitum to produce a delegation
46             heirachy which allows for powerful and complex systems that are relatively
47             easy to maintain.
48              
49             This can be particularly noticable as the scale of a POE codebase gets
50             larger. At three levels of abstraction the layering in POE becomes quite
51             difficult, and progess past about the third layer of abstraction becomes
52             extremely difficult.
53              
54             B is an attempt to resolve this problem by locking down
55             part of the traditional flexibility of POE, and by making it easier
56             to split the implementation of each object between an object-oriented
57             heirachy and a collection of POE sessions.
58              
59             The goal is to retain the ability to build deep and complex heirachies
60             of encapsulated functionality in your application while also allowing
61             you to take advantage of the asynchronous nature of POE code.
62              
63             =head2 General Architecture
64              
65             At the core of any B application is an object-oriented
66             heirachy of components. This heirachy exists whether or not the POE
67             kernel is running, and parts of it can be started and stopped as needed.
68              
69             When it is spawned, each component will create it's own private
70             L in which to run its events and store its resources.
71              
72             Each instance of a class always has one and only one session. This may
73             be a problem if your application will have thousands of spawned components
74             at once (POE recommends against the use of large numbers of sessions) but
75             should not be a problem as long as you keep it down to a few hundred
76             components.
77              
78             Because the POE session is slaved to the L, the
79             component can handle being started and stopped many times without the
80             loss of any state data between the creation and destruction of each
81             slave session.
82              
83             To allow support for components that have no resources and only act as
84             supervisors, B always assigns a POE alias for the session
85             while it is active. The existance of this Alias prevents POE cleaning up
86             the session accidentally, and ensures components have explicit control
87             over when they want to shut down their sessions.
88              
89             Each POE component contains a set of named resources. Resources may
90             consist of a different underlying POE resource, or may be made up of
91             multiple resources, and additional data stored in the matching object
92             HASH key. To ensure all the various underlying resources will not clash
93             with each other, all resources must be declared and will be strictly
94             checked.
95              
96             At the end of each class, instead of the usual 1; to allow the package to
97             return true, you put instead a "compile;" statement.
98              
99             This instructs L to inventory the declarations and attributes,
100             combine them with declarations from the parent classes, and then generate
101             the code that will implement the structures.
102              
103             Once the class has been compiled, the installed functions will be removed
104             from the package to prevent run-time namespace pollution.
105              
106             =head2 Import-Time Declaration
107              
108             The cluster of "declare" statements at the beginning of a B
109             class can look ugly to some people, and may get annoying to step through in
110             the debugger.
111              
112             To resolve this, you may optionally provide a list of slot declarations to
113             the module at compile time. This should be in the form of a simple C
114             reference with the names as keys and the type as values.
115              
116             use My::Module {
117             Foo => 'Param',
118             Bar => 'Param',
119             };
120              
121             Event and timeout declarations cannot be provided by this method, and you
122             should continue to use subroutine attributes for these as normal.
123              
124             =head2 Inheritance
125              
126             The resource model of L correctly follows inheritance,
127             similar to the way declarations in L are inherited. Resource types
128             in a parent class cannot be overwritten or modified in child classes.
129              
130             No special syntax is needed for inheritance, as L works
131             directly on top of Perl's native inheritance.
132              
133             # Parent.pm - Object that connects to a service
134             package My::Parent;
135            
136             use strict;
137             use POE::Declare {
138             Host => 'Param',
139             Port => 'Param',
140             ConnectSuccess => 'Message',
141             ConnectFailure => 'Message',
142             };
143            
144             sub connect : Event {
145             # ...
146             }
147            
148             compile;
149            
150             # Child.pm - Connect to an (optionally) authenticating service
151             package My::Child;
152            
153             use strict;
154             use base 'My::Parent';
155             use POE::Declare {
156             Username => 'Param',
157             Password => 'Param',
158             AuthRequired => 'Message',
159             AuthInvalid => 'Message',
160             };
161            
162             compile;
163              
164             =head1 CLASSES
165              
166             B is composed of three main modules, and a tree of
167             slot/attribute classes.
168              
169             =head2 POE::Declare
170              
171             B provides the main interface layer and Domain
172             Specific API for declaratively building your POE::Declare classes.
173              
174             =head2 POE::Declare::Object
175              
176             L is the abstract base class for all classes created
177             by B.
178              
179             =head2 POE::Declare::Meta
180              
181             L implements the metadata structures that describe
182             each of your B classes. This is superficially similar to
183             something like L, but unlike Moose is fast, light weight and
184             can use domain-specific assumptions.
185              
186             =head2 POE::Declare::Slot
187              
188             POE::Declare::Meta::Slot
189             POE::Declare::Meta::Internal
190             POE::Declare::Meta::Attribute
191             POE::Declare::Meta::Param
192             POE::Declare::Meta::Message
193             POE::Declare::Meta::Event
194             POE::Declare::Meta::Timeout
195              
196             =head2 POE::Declare::Meta::Internal
197              
198             L is a slot class that won't generate any
199             functionality, but allows you to reserve an attribute for internal use
200             so that they won't be used by any sub-classes.
201              
202             =head2 POE::Declare::Meta::Attribute
203              
204             L is a slot class used for readable
205             attributes.
206              
207             =head2 POE::Declare::Meta::Param
208              
209             L is a slot class for attributes that
210             are provided to the constructor as a parameter.
211              
212             =head2 POE::Declare::Meta::Message
213              
214             L is a slot class for declaring messages that
215             the object will emit under various circumstances. Each message is a param
216             to the constructor that takes a callback in a variety of formats (usually
217             pointing up to the parent object).
218              
219             =head2 POE::Declare::Meta::Event
220              
221             L is a class for named POE events that can be
222             called or yielded to by other POE messages/events.
223              
224             =head2 POE::Declare::Meta::Timeout
225              
226             L is a L sub-class
227             that is designed to trigger from an alarm and generates additional methods
228             to manage the alarms.
229              
230             =head1 FUNCTIONS
231              
232             For the first few releases, I plan to leave this module undocumented.
233              
234             That I am releasing this distribution at all is more of a way to
235             mark my progress, and to allow other POE/OO people to look at the
236             implementation and comment.
237              
238             =cut
239              
240 6     6   249973 use 5.008007;
  6         20  
  6         186  
241 4     4   22 use strict;
  4         8  
  4         131  
242 4     4   22 use warnings;
  4         22  
  4         131  
243 4     4   24 use Carp ();
  4         6  
  4         78  
244 4     4   21 use Exporter ();
  4         7  
  4         79  
245 4     4   22 use List::Util 1.19 ();
  4         105  
  4         105  
246 4     4   4089 use Params::Util 1.00 ();
  4         19031  
  4         151  
247 4     4   3806 use POE::Session ();
  4         17630  
  4         94  
248 4     4   3061 use POE::Declare::Meta ();
  4         13  
  4         100  
249 4     4   2752 use POE 1.310;
  4         152997  
  4         30  
250              
251             # The base class requires POE::Declare to be fully compiled,
252             # so load it in post-BEGIN with a require rather than at
253             # BEGIN-time with a use.
254             require POE::Declare::Object;
255              
256             # Provide the SELF constant
257 4     4   374437 use constant SELF => HEAP;
  4         81  
  4         254  
258              
259 4     4   21 use vars qw{$VERSION @ISA @EXPORT %ATTR %EVENT %META};
  4         9  
  4         494  
260             BEGIN {
261 4     4   8 $VERSION = '0.59';
262 4         70 @ISA = qw{ Exporter };
263 4         12 @EXPORT = qw{ SELF declare compile };
264              
265             # Metadata Storage
266 4         8 %ATTR = ();
267 4         9 %EVENT = ();
268 4         218 %META = ();
269             }
270              
271              
272              
273              
274              
275             #####################################################################
276             # Declaration Functions
277              
278             sub import {
279 5     5   2483 my $pkg = shift;
280 5         20 my $callpkg = caller($Exporter::ExportLevel);
281              
282             # POE::Declare should only be loaded on empty classes.
283             # We only use the simple case here of checking for $VERSION or @ISA
284 4     4   22 no strict 'refs';
  4         7  
  4         4516  
285 5 50       68 if ( defined ${"$callpkg\::VERSION"} ) {
  5         43  
286 0         0 Carp::croak("$callpkg already exists, cannot use POE::Declare");
287             }
288 5 100       10 if ( @{"$callpkg\::ISA"} ) {
  5         31  
289             # Are we a subclass of an existing POE::Declare class
290 2 50       25 unless ( $callpkg->isa('POE::Declare::Object') ) {
291             # This isn't a POE::Declare class
292 0         0 Carp::croak("$callpkg already exists, cannot use POE::Declare");
293             }
294             } else {
295             # Set @ISA for the package, which does most of the work
296             # We have to set this early, otherwise attribute declaration
297             # won't work.
298 3         7 @{"$callpkg\::ISA"} = qw{ POE::Declare::Object };
  3         56  
299             }
300              
301             # Set a temporary meta function that will throw an exception
302 5         26 *{"$callpkg\::meta"} = sub {
303 2     2   77 Carp::croak("POE::Declare class $callpkg has not called compile()");
304 5         28 };
305              
306             # Export the symbols
307 5         12 local $Exporter::ExportLevel += 1;
308 5         210 $pkg->SUPER::import();
309              
310             # Make "use POE::Declare;" an implicit "use POE;" as well
311 5     3   364 eval "package $callpkg; use POE;";
  3         18  
  3         7  
  3         19  
312 5 50       1658 die $@ if $@;
313              
314             # If passed a HASH structure, treat them as a set of declare
315             # calls so that the slots can be defined quickly and in a single
316             # step during the load.
317 5 100       42 if ( Params::Util::_HASH($_[0]) ) {
318 2         4 my %declare = %{$_[0]};
  2         12  
319 2         10 foreach my $name ( sort keys %declare ) {
320 6         13 _declare( $callpkg, $name, $declare{$name} );
321             }
322             }
323              
324 5         5454 return 1;
325             }
326              
327             =pod
328              
329             =head2 declare
330              
331             declare one => 'Internal';
332             declare two => 'Attribute';
333             declare three => 'Param';
334             declare four => 'Message';
335              
336             The C function is exported by default. It takes two parameters,
337             a slot name and a slot type.
338              
339             The slot name can be any legal Perl identifier.
340              
341             The slot type should be one of C, C, C or
342             C.
343              
344             Creates the new slot, throws an exception on error.
345              
346             =cut
347              
348             sub declare (@) {
349 10     10 1 3120 my $pkg = caller();
350 10         134 local $Carp::CarpLevel += 1;
351 10         31 _declare( $pkg, @_ );
352             }
353              
354             sub _declare {
355 16     16   29 my $pkg = shift;
356 16 50       56 if ( $META{$pkg} ) {
357 0         0 Carp::croak("Too late to declare additions to $pkg");
358             }
359              
360             # What is the name of the attribute
361 16         24 my $name = shift;
362 16 50       468 unless ( Params::Util::_IDENTIFIER($name) ) {
363 0         0 Carp::croak("Did not provide a valid attribute name");
364             }
365              
366             # Has the attribute already been defined
367 16 50       178 if ( $ATTR{$pkg}->{$name} ) {
368 0         0 Carp::croak("Attribute $name already defined in class $pkg");
369             }
370              
371             # Resolve the attribute class
372 16         23 my $type = do {
373 16         22 local $Carp::CarpLevel += 1;
374 16         38 _attribute_class(shift);
375             };
376              
377             # Is the class an attribute class?
378 16 50       189 unless ( $type->isa('POE::Declare::Meta::Slot') ) {
379 0         0 Carp::croak("The class $type is not a POE::Declare::Slot");
380             }
381              
382             # Create and save the attribute
383 16         144 $ATTR{$pkg}->{$name} = $type->new(
384             name => $name,
385             @_,
386             );
387              
388 16         49 return 1;
389             }
390              
391             # Resolve an attribute type
392             sub _attribute_class {
393 16     16   25 my $type = shift;
394 16 50       409 if ( Params::Util::_IDENTIFIER($type) ) {
    0          
395 16         135 $type = "POE::Declare::Meta::$type";
396             } elsif ( Params::Util::_CLASS($type) ) {
397 0         0 $type = $type;
398             } else {
399 0         0 Carp::croak("Invalid attribute type");
400             }
401              
402             # Try to load the attribute class
403 16         42 my $file = $type . '.pm';
404 16         67 $file =~ s{::}{/}g;
405 16         30 eval { require $file };
  16         81  
406 16 50       44 if ( $@ ) {
407 0         0 local $Carp::CarpLevel += 1;
408 0         0 my $quotefile = quotemeta $file;
409 0 0       0 if ( $@ =~ /^Can\'t locate $quotefile/ ) {
410 0         0 Carp::croak("The attribute class $type does not exist");
411             } else {
412 0         0 Carp::croak($@);
413             }
414             }
415              
416 16         39 return $type;
417             }
418              
419             =pod
420              
421             =head2 compile
422              
423             The C function indicates that all attributes and events have
424             been defined and the structure should be finalised and compiled.
425              
426             Returns true or throws an exception.
427              
428             =cut
429              
430             sub compile () {
431 8     8 1 1029 my $pkg = caller();
432              
433             # Shortcut if already compiled
434 8 50       123 return 1 if $META{$pkg};
435              
436             # Create the meta object
437 8         75 my $meta = $META{$pkg} = POE::Declare::Meta->new($pkg);
438 8         39 my @super = reverse $meta->super_path;
439              
440             # Make sure any parent POE::Declare classes are compiled
441 8         294 foreach my $parent ( @super ) {
442 14 50       85 next if $META{$parent};
443 0         0 Carp::croak("Cannot compile $pkg, parent class $parent not compiled");
444             }
445              
446             # Are any attributes already defined in our parents?
447 8         21 foreach my $name ( sort keys %{$ATTR{$pkg}} ) {
  8         63  
448             my $found = List::Util::first {
449 32     32   71 $ATTR{$_}->{attr}->{$name}
450 16         123 } @super;
451 16 50       69 Carp::croak(
452             "Duplicate attribute '$name' already defined in "
453             . $found->name
454             ) if $found;
455 16         58 $meta->{attr}->{$name} = $ATTR{$pkg}->{$name};
456             }
457              
458             # Attempt to compile all the individual parts
459 8         50 $meta->as_perl;
460             }
461              
462             # Get the meta-object for a class.
463             # Primarily used for testing purposes.
464             sub meta {
465 6     6 0 3983 $META{$_[0]};
466             }
467              
468             sub next_alias {
469 0     0 0 0 my $meta = $META{$_[0]};
470 0 0       0 unless ( $meta ) {
471 0         0 Carp::croak("Cannot instantiate $_[0], class not defined");
472             }
473 0         0 $meta->next_alias;
474             }
475              
476             1;
477              
478             =pod
479              
480             =head1 SUPPORT
481              
482             Bugs should be always be reported via the CPAN bug tracker at
483              
484             L
485              
486             For other issues, or commercial enhancement or support, contact the author.
487              
488             =head1 AUTHORS
489              
490             Adam Kennedy Eadamk@cpan.orgE
491              
492             =head1 SEE ALSO
493              
494             L, L
495              
496             =head1 COPYRIGHT
497              
498             Copyright 2006 - 2012 Adam Kennedy.
499              
500             This program is free software; you can redistribute
501             it and/or modify it under the same terms as Perl itself.
502              
503             The full text of the license can be found in the
504             LICENSE file included with this module.
505              
506             =cut