In Magpie, application classes are implemented as subclasses of an event model. This underlying Event
class is responsible for registering event handlers with Magpie's internal queue and for determining
which of those registered handlers will be fired in response to the current state. In short, the Event
class determines which state the application is in, and which of the registered event handler methods
will be fired in response to that state. Usually, the details of mapping states to events are never
visible to the developer beyond the initial choice of which Event model to use as a base class (different
Event classes use different conditions to determine application state). In daily practice, you simply
implement and register the events that you application needs and let Magpie do the rest.
# Greetings.pm -- A Magpie Application class that greets the
# user based on application state
package MyApp::Pipeline::Greetings;
use Moose;
# inherit from the base Component class.
extends 'Magpie::Component';
# determines the application state based on the value
# of a specific form/query param ala CGI::Application.
with 'Magpie::Dispatcher::RequestParam' => { state_param => 'appstate' };
# Import handler
use Magpie::Constants;
# register the event handlers for this class
__PACKAGE__->register_events( qw( morning afternoon evening default) );
# implement the event handlers
sub morning {
my $self = shift;
my $ctxt = shift;
$ctxt->{message} = 'Good morning!';
return OK;
}
sub afternoon {
my $self = shift;
my $ctxt = shift;
$ctxt->{message} = 'Good afternoon!';
return OK;
}
sub evening {
my $self = shift;
my $ctxt = shift;
$ctxt->{message} = 'Good evening!';
return OK;
}
# will be called if no matching state is found
sub default {
my $self = shift;
my $ctxt = shift;
$ctxt->{message} = "I'm not sure what time of day it is!";
return OK;
}
1;
First, notice that the application class is a subclass of the "Magpie::Component". Through this
interface (and the roles it consumes), you get access to the core attributes and methods of the Magpie
application framework (see the section titled 'know thy $self' below). In "Magpie::Event::Simple" you
register a list of state events via the required registerEvents() function. The event model then
determines which event handler method to fire by examining the value of a specific querystring or POSTed
form parameter. (The default param is named "appstate" but that can be overridden by implementing the
state_param() method and returning some other value). If the underlying Event model finds a registered
event whose name matches the value returned by the state_param() method, the event handler method named
"event_<eventname"> is called. So, for example, a request to the URI that exposes the class above like
the following:
http://example.org/apps/greet?appstate=morning
would cause the event_morning() method to be called. If no matching state is found, the event_default()
event handler method is called as a fallback. In addition, most Magpie Event model classes also
implement an event_init() handler and an event_exit() handler. These methods are optional and, if
implemented, event_init() will be called after the event queue is initialized but before the first state-
determined event handler is fired while event_exit() is called just before the application exits.
Note that "Magpie::Event::Simple" (that determines application state based on a single form param) is
only one possible event model and you are free to choose another and/or write your own-- even mixing
application classes based on different models within the same application pipeline. This flexibility is
one of Magpie's key strengths.
EventHandlerMethods
Every Magpie Application class will implement one or more eventhandlermethods that will be called
conditionally based on the state that is determined by the underlying event model. It is within these
methods that the real business of the application takes place. In the above example, these methods merely
set a key in the application-wide $ctxt hash reference, but there is no limit to what you can do.
Remember, part of the point of Magpie is to separate state detection from application code-- this is
achieved by having the event model parent class determine the state then call the event handler methods
that implement the code that should be run for that state.
Each event handler method is passed two arguments: the $self class instance member, and a special
applicationcontext member (named $ctxt in the examples above).
KnowThy$self
In Magpie Application classes the $self class instance member offers access to a handful of common
attributes and methods:
"$self->request"
This offers acccess to "Plack::Request" object representing the current client request.
Example:
if ( $self->request->method eq 'POST' ) {
...
}
KeepingThingsIn$ctxt
The second argument passed to each event handler method is the contextmember (named $ctxt in these
examples). The context member-- which can be any kind of Perl object or reference to another data
structure-- is passed as the sole argument to the application pipeline's run() method.
# in your interface module/script:
my $handler = builder {
enable "Magpie", context => $app_context, pipeline => [
...
];
};
...
# then later, in the event handler methods in your application classes
sub myevent {
my $self = shift;
my $ctxt = shift; # same object/data as $app_context above
}
In keeping with Magpie's general goal of letting developers do what makes the most sense to them, Magpie
does not enforce many rules about what the $ctxt can be, or how it can be used. The only constraint is
that the context member must be a scalar or reference. Typically, the context member is a reference to a
Perl hash, an anonymous hash reference, or a blessed object. Again, some examples that might appear in
your in your interface module/script:
# use a reference to an existing hash
my %context = (
template_dir => '/usr/local/my/app/templates',
default_template => 'index.xsl',
);
my $handler = builder {
enable "Magpie", context => \%context, pipeline => [
...
];
};
# some advanced apps do well to make the context an object
# that implements is own set of methods
my $context = My::Application::ContextMember->new( %args );
my $handler = builder {
enable "Magpie", context => $context, pipeline => [
...
];
};
When no context member is explicitly passed into the Magpie machine an anonymous hash reference is used
as a fallback.
# no $ctxt is passed in
my $handler = builder {
enable "Magpie", pipeline => [
...
];
};
# then later...
sub myevent {
my $self = shift;
my $ctxt = shift; # now an anonymous hashref
}
Obviously, the role of the context member will vary greatly depending upon your coding style and the
needs of the application. In general, though, the most common use of the $ctxt is to accumulate the data
needed to render the proper output for the current request. For example, if you are using the Template
Toolkit Transformer class (Magpie::Transformer::TT2) you might use a plain hash reference as the context
member, then use it to capture the template name and variables that your templates depend on to deliver
the content.
EventHandlerReturnCodes
Each event handler method must return one of a number of eventhandlerreturncodes. The codes signal
Magpie's internal event loop about what to do after the current event handler method is finished. The
codes themselves are numeric, but are implemented as convenient Perl constants via the
"Magpie::Constants" module so you do not have to try to remember what the numeric codes are (this is
similar to the way the "Apache::Constants" module works for HTTP return codes).
use Magpie::Constants;
sub myevent {
my $self = shift;
my $ctxt = shift;
# do a little dance...
return OK;
}
The most common return codes and their effects on the application's behavior are as follows:
"OK"
Returning "OK" from you event handler method signals Magpie that everything went as expected during
the method's run and that it is safe to continue.
sub event_init {
my $self = shift;
my $ctxt = shift;
# actual application behavior here
# everything went well, continue on...
return OK;
}
"DECLINED"
Returning "DECLINED" from your event handler method tells Magpie's internal event queue to skip to
the next application or output class in the pipeline. Any other methods in the current application
class that would usually be fired based on the current state will be skipped.
sub init {
my $self = shift;
my $ctxt = shift;
unless ( defined $ctxt->{some_required_data} ) {
# we don't have the data we need to continue
$ctxt->{error_message} = "Insufficient data for init event";
return DECLINED;
}
# otherwise continue on...
return OK;
}
"OUTPUT"
Where the "DECLINED" return code signals Magpie to skip to the verynext class in the pipeline,
returning "OUTPUT" from your event handler method tells Magpie's internal event queue to skip to verylast class in the application pipeline (which is presumed to be the Output class for the current
application).
sub event_init {
my $self = shift;
my $ctxt = shift;
unless ( my $user = $self->query->cookie('app_user') ) {
# like DECLINED above, but set a redirect
# header and skip directly to Output phase
$self->redirect('/login.xml');
return OUTPUT;
}
# otherwise continue on...
return OK;
}
"DONE"
Returning "DONE" from your event handler method stops the application pipeline dead in its tracks.
All subsequent classes that may be in the pipeline are skippedIt is rarely used, given it typically
stops the application before any data is sent to the client, but it can be useful for sending
appropriate HTTP response codes.
sub event_init {
my $self = shift;
my $ctxt = shift;
unless ( -f $ctxt->{some_required_file} ) {
# we don't have the some crucial file needed to proceed
# so throw a 404 Not Found response while stopping the
# application
$self->response->status(404);
return DONE;
}
# otherwise continue on...
return OK;
}