NAME
SweetPea - A web framework that doesn't get in the way, or suck.
VERSION
Version 2.13
SYNOPSIS
Oh how Sweet web application development can be ...
... at the cli (command line interface)
# download, test and install
cpan SweetPea
# build your skeleton application
cd web_server_root/htdocs/my_new_application
perl -MSweetPea -e makeapp
> Created file /sweet/App.pm (chmod 755) ...
> Created file /.pl (chmod 755) ...
> Created file /.htaccess (chmod 755) ...
> Creat....
> ...
# in the generated .pl file (change the path to perl if neccessary)
#!/usr/bin/perl -w
use SweetPea;
my $s = SweetPea->new->run;
That's all Folks.
DESCRIPTION
SweetPea is a modern web application framework that follows the MVC (Model, View, Controller) design pattern using useful concepts from Mojolicious, Catalyst and other robust web frameworks. SweetPea has a short learning curve, is light-weight, as scalable as you need it to be, and requires little configuration.
EXPORTED
makeapp (skeleton application generation)
HOW IT WORKS
SweetPea uses a simple MVC pattern and ideology for processing and responding to requests from the users browser. Here is an example request and response outlining how SweetPea behaves when a request is received.
# The request
http://localhost/admin/auth/
- The user requests http://localhost/admin/auth/ from the browser.
# The simple MVC pattern
http://localhost/(admin/auth/)
- admin/auth either matches as a Controller or Controller/Action.
- e.g. Controller::Admin::auth() or Controller::Admin::Auth::_index()
# The response
- .pl (dispatcher/router) invokes SweetPea->new->run
- the run method loads all plugins and scans the controllers folder
building a table of controller/actions for further dispatching.
- the dispatching routine executes the global or local _begin method,
then executes the action or global or local _index method, and
finally executes the global or local _end method.
- the start and finish methods are then called to create, render
and finalize the response and output.
# Other magic (not included)
* SweetPea will support routing which is a very popular way of
dispatching URLs. Using routes will disable the default method
of discovering controllers and actions making the application
more secure. SweetPea will default scanning the controllers
folder if no routes are defined.
APPLICATION STRUCTURE
/static ## static content (html, css) is stored here
/sweet ## application files are stored here
/application ## MVC files are stored here
/Controller ## controllers are stored here
Root.pm ## default controller (should always exist)
Sweet.pm ## new application welcome page controller
/Model ## models are stored here
Schema.pm ## new application boiler-plate model
/View ## views are stored here
Main.pm ## new application boiler-plate view
/sessions ## auto-generated session files are stored here
/templates ## templates and layouts can be stored here
App.pm ## module for loading plugins (other modules)
/.htaccess ## enables pretty-urls on apache w/mod-rewrite
/.pl ## default dispatcher (controller/action router)
GENERATED FILES INFORMATION
sweet/application/Controller/Root.pm
Controller::Root
The Root.pm controller is the default controller similar in function to
a directory index (e.g. index.html). When a request is received that can
not be matched in the controller/action table, the root/index
(or Controller::Root::_index) method is invoked. This makes the _index
method of Controller::Root, a kind of global fail-safe or fall back
method.
The _begin method is executed before the requested action, if no action
is specified in the request the _index method is used, The _end method
is invoked after the requested action or _index method has been
executed.
The _begin, _index, and _end methods can exist in any controller and
serves the same purposes described here. During application request
processing, these special routines are checked for in the namespace of
the current requested action's Controller, if they are not found then
the (global) alternative found in the Controller::Root namespace will
be used.
# Controller::RootRoot.pm
package Controller::Root;
sub _begin { my ( $self, $s ) = @_; }
sub _index { my ( $self, $s ) = @_; }
sub _end { my ( $self, $s ) = @_; }
1;
sweet/application/Controller/Sweet.pm
Controller::Sweet
# Sweet.pm
* A welcome page for the newly created application. (Safe to delete)
sweet/application/Model/Schema.pm
Model::Schema
# Model/Schema.pm
The Model::Schema boiler-plate model package is were your data
connection, accessors, etc can be placed. SweetPea does not impose
a specific configuration style, please feel free to connect to your
data in the best possible fashion. Here is an example of how one
might use this empty package with DBIx::Class.
# in Model/Schema.pm
package Model::Schema;
use base qw/DBIx::Class::Schema::Loader/;
__PACKAGE__->loader_options(debug=>1);
1;
# in App.pm
use Model::Schema;
sub plugins {
$base->plug('data', sub { shift; return Model::Schema->new(@_) });
}
# example usage in Controller/Root.pm
sub _dbconnect {
my ($self, $s) = @_;
$s->data->connect($dbi_dsn, $user, $pass, \%dbi_params);
}
sweet/application/View/Main.pm
View::Main
# View/Main.pm
The View::Main boiler-plate view package is were your layout/template
accessors and renders might be stored. Each view is in fact a package
that determines how data should be rendered back to the user in
response to the request. Examples of different views are as follows:
View::Main - Main view package that renders layouts and templates
based on the main application's user interface design.
View::Email::HTML - A view package which renders templates to
be emailed as HTML.
View::Email::TEXT - A view package which renders templates to be
emailed as plain text.
Here is an example of how one might use this empty
package with Template (template toolkit).
# in View/Main.pm
package View::Main;
use base Template;
sub new {
return __PACKAGE__->e->new({
INCLUDE_PATH => 'sweet/templates/',
EVAL_PERL => 1,
});
}
1;
# in App.pm
use View::Main;
sub plugins {
$base->plug('view', sub{ shift; return View::Main->new(@_) });
}
# example usage in Controller/Root.pm
sub _index {
my ($self, $s) = @_;
$s->view->process($input, { s => $s });
}
sweet/application/App.pm
App
# App.pm
The App application package is the developers access point to
configure and extend the application before request processing. This
is typically done using the plugins method. This package contains
the special and required plugins method. Inside the plugins method is
were other Modules are loaded and Module accessors are created using
the core "plug" method. The following is an example of App.pm usage.
package App;
use warnings;
use strict;
use HTML::FormFu;
use HTML::GridFu;
use Model::Schema;
use View::Main;
sub plugins {
my ( $class, $base ) = @_;
my $self = bless {}, $class;
$base->plug( 'form', sub { shift; return HTML::FormFu->new(@_) } );
$base->plug( 'data', sub { shift; return Model::Schema->new(@_) } );
$base->plug( 'view', sub { shift; return View::Main->new(@_) } );
$base->plug( 'grid', sub { shift; return HTML::GridFu->new(@_) } );
return $self;
}
1; # End of App
.htaccess
htaccess
# .htaccess
The .htaccess file allows apache-type web servers that support
mod-rewrite to automatically configure your application environment.
Using mod-rewrite your application can make use of pretty-urls. The
requirements for using .htaccess files with your SweetPea application
are as follows:
mod-rewrite support
.htaccess support with Allow, Deny
# in .htaccess
DirectoryIndex .pl
AddHandler cgi-script .pl .pm .cgi
Options +ExecCGI +FollowSymLinks -Indexes
RewriteEngine On
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteRule (.*) .pl/$1 [L]
.pl
pl
# .pl
The .pl file is the main application router/dispatcher. It is
responsible for prepairing the application via executing all pre and
post processing routines as well as directing requests to the
appropriate controllers and actions.
#!/usr/env/perl
BEGIN {
use FindBin;
use lib $FindBin::Bin . '/sweet';
use lib $FindBin::Bin . '/sweet/application';
}
use SweetPea;
use App;
SweetPea->new->run;
SPECIAL ROUTINES
_begin
# _begin
sub _begin {...}
The begin method can exist both globally and locally, and will be
automatically invoked per request. When a request is processed,
SweetPea checks whether the _begin method exists in the namespace
of the Controller being requested, if not it checks whether the
_begin method exists in the Controller::Root namespace and
executes that method. If you opt to keep and use the default
controller Controller::Root, then its _begin method will be
defined as the global _begin method and will be executed
automatically with each request. The automatic execution of
_begin in Controller::Root can be overridden by adding a _begin
method to the namespace of the controller to be requested.
This special method is useful for checking user permissions, etc.
_index
# _index
sub _index {...}
The index method can exist both globally and locally, and will
be automatically invoked *only* if an action is not specified.
When a request is processed, SweetPea scans the controllers
folder building a table of controllers and actions for
dispatching. The dispatching routine executes attempts to
execute the action, if no action is specified, it
default to executing the global or local _index method
looking locally first, then globally ofcourse. The automatic
execution of _index in Controller::Root can be overridden by
adding a _index method to the namespace of the controller to
be requested.
This special method acts as a directory index or index.html
file in that it is executed when no other file (action) is
specified.
_end
# _end
sub _end {...}
The end method can exist both globally and locally, and will be
automatically invoked per request. When a request is processed,
SweetPea checks whether the _end method exists in the namespace
of the Controller being requested, if not it checks whether the
_end method exists in the Controller::Root namespace and
executes that method. If you opt to keep and use the default
controller Controller::Root, then its _end method will be
defined as the global _end method and will be executed
automatically with each request. The automatic execution of
_end in Controller::Root can be overridden by adding a _end
method to the namespace of the controller to be requested.
This special method is useful for performing cleanup
functions at the end of a request.
RULES AND SYNTAX
The anatomy of a controller method
Controllers are used by SweetPea in an OO (object-oriented)
fashion and thus, all controller methods should follow the
same design as they are passed the same parameters.
package Controller::Foo;
sub bar {
my ($self, $s) = @_;
...
}
1;
The foo method above (as well as al other controller methods)
are passed at least two objects, an instance of the current
controller usually referred to as $self, and an instance of
the SweetPea application object usually referred to as $s.
Note! Actions prefixed with an underscore can not be
displatched to using URLs.
How to use plugins (other modules)
Plugins are a great way to extend the functionality of a
SweetPea application. Plugins are defined in the application
package App.pm inside of the special plugins method as
follows:
# inside of App.pm
package App;
...
use CPAN::Module;
sub plugins {
my ( $class, $base ) = @_;
my $self = bless {}, $class;
$base->plug( 'cpan', sub { shift; return CPAN::Module->new(@_) } );
return $self;
}
...
# notice below how an accessor is created for the ficticious
CPAN::Module in the SweetPea namespace
# inside sweet/Controller/MyController.pm
sub _index {
my ($self, $s) = @_;
$s->cpan->some_method(...);
}
# when $s->cpan is called, it creates (unless the object reference
exists) and returns a reference to that module object. To create
or initialize another object, simply call the unplu method on the
object's name.
# inside sweet/Controller/MyController.pm
sub _index {
my ($self, $s) = @_;
my $foo = $s->cpan;
my $bar = $s->cpan;
my $baz = $s->unplug('cpan')->cpan;
}
# in the example above, $foo and $bar hold the same reference, but
$baz is holding a new refernce as if it called CPAN::Module->new;
INSTANTIATION
new
The new method initializes a new SweetPea object.
# in your .pl or other index/router file
my $s = SweetPea->new;
run
The run method discovers
controllers and actions and executes internal pre and post request processing
routines.
# in your .pl or other index/router file
my $s = SweetPea->new->run; # start processing the request
NOTE! CGI, CGI::Cookie, and CGI::Session are plugged in automatically
by the run method.
# accessible via $s->cgi, $s->cookie, and $s->session
CONTROLLERS AND ACTIONS
Controllers are always created in the sweet/controller folder and defined
under the Controller namespace, e.g. Controller::MyController. In keeping
with simplicity, controllers and actions are actually packages and
routines ( controller/action = package controller; sub action {...} ).
NOTE! Actions prefixed with an underscore e.g. _foo can not be dispatched to
using URLs but are listed in the dispatch table and are available to
the forward, detach and many other methods that might invoke an
action/method.
HELPER METHODS
makeapp
This function is exported an intended to be called from the
command-line. This creates the boiler plate appication structure.
# e.g. at the command line
perl -MSweetPea -e makeapp
controller
The action method returns the current requested MVC
controller/package.
# user requested http://localhost/admin/auth
$controller = $s->controller
# $controller is /admin
$controller = $s->controller('services');
# $controller is /admin/services
# maybe useful for saying
$s->forward( $s->controller('services') );
# executes Controller::Admin::services()
action
The action method returns the current requested MVC
action.
# user requested http://localhost/admin/auth
$action = $s->action
# $action is auth if auth is an action, blank if not
url/uri
The url/uri methods returns a completed URI string
or reference to root, here or path variables, e.g.
# user requested http://localhost/admin/auth
my $link = $s->url('static/index.html');
# $link is http://localhost/static/index.html
# $s->uri->{root} is http://localhost
# $s->uri->{here} is http://localhost/admin/auth
# $s->uri->{path} is /admin/auth
path
The path method returns a completed path to root
or location passed to the path method.
# application lives at /domains/sweetapp
my $path = $s->path;
# $path is /domains/sweetapp
my $path = $s->path('static/index.html');
# $path is /domains/sweetapp/static/index.html
cookies
Returns an array of cookies set throughout the request.
...
foreach my $cookie (@{$s->cookies}) {
# do something with the cookie data
}
CONTROL METHODS
start
The start method should probably be named (startup) because
it is the method which processes the request and performs
various startup tasks.
# is invoked automatically
finish
The finish method performs various tasks in processing the
response to the request.
# is invoked automatically
forward
The forward method executes a method in a namespace,
then continues to execute instructions in the method it was
called from.
# in Controller::Admin
sub auth {
my ($self, $s) = @_;
$s->forward('/admin/auth_success');
# now im doing something else
}
sub auth_success {
my ($self, $s) = @_;
# im doing something
}
using forward to here was inefficient, one could have used
$self->auth_success($s) because we are in the same package.
detach
The detach method executes a method in a namespace, then
immediately executes the special "_end" method which finalizes
the request.
# in Controller::Admin
sub auth {
my ($self, $s) = @_;
$s->detach('/admin/auth_success');
# nothing after is executed
}
sub auth_success {
my ($self, $s) = @_;
# im doing something
}
using forward to here was inefficient, one could have used
$self->auth_success($s) because we are in the same package.
METHODS
store
The store method is an accessor to the special "store"
hashref. The store hashref is the functional equivilent
of the stash method found in many other frameworks. It
serves as place developers can save and retreive information
throughout the request.
$s->store->{important_stuff} = "This is top secret stuff";
application
The application method is in accessor to the special
"application" hashref. As the "store" hashref is where general
application data is stored, the "application" hashref is where
application configuration information is stored.
$s->application->{content_type} = 'text/html';
This is just an example, to change the content type please use
$s->contenttype('text/html');
Content-Type is always 'text/html' by default.
contenttype
The contenttype method set the desired output format for use
with http response headers.
$s->contenttype('text/html');
html
The html method sets data to be output to the browser or if
called with no parameters returns the data recorded and
clears the data store.
If the html store contains any data at the end of the request,
it is output to the browser.
# in Controller::Root
sub _index {
my ($self, $s) = @_;
$s->html('this is a test');
$self->my_two_cents($s);
}
sub my_two_cents {
my ($self, $s) = @_;
$s->html(', or maybe not');
}
"this is a test, or maybe not" is output to the browser
# in Controller::Root
my @data;
sub _index {
my ($self, $s) = @_;
$s->html('this is a test');
$self->forget_it($s);
}
sub forget_it {
my ($self, $s) = @_;
@data = @{$s->html};
}
Nothing is output to the browser as $s->html returns and
array of data stored in it and clears itself
# @data contains ['this is a test','or maybe not']
debug
The debug method sets data to be output to the browser with
additional information for debugging purposes or if called
with no parameters returns the data recorded and clears the
data store. debug() is the functional equivilent of html()
but with a different purpose.
output
This method spits out html or debug information stored using
$s->html and/or $s->debug methods throughout the request. The
output method takes one argument, an entry seperator, which
if defined (empty or not) will output debug data, if not
explicitly defined will output html data.
$s->output;
# outputs html data.
$s->output(""); or $s->output("\n"); or $s->output("<br/>");
# outputs debug data.
plug
The plugin method creates accessors for third party (non-core)
modules, e.g.
$self->plug('email', sub{ shift; return Email::Stuff->new(@_) });
# allow you to to say
# in Controller::Root
sub _index {
my ($self, $s) = @_;
$self->email->to(...)->from(...)->etc...
}
# NOTE! plugins should be defined within the plugins methods of
the App.pm package;
unplug
The unplug method releases the reference to the module object
used by the module accessor created by the plug method.
# inside sweet/Controller/MyController.pm
sub _index {
my ($self, $s) = @_;
my $foo = $s->cpan;
my $bar = $s->cpan;
my $baz = $s->unplug('cpan')->cpan;
}
# in the example above, $foo and $bar hold the same reference, but
$baz is holding a new refernce as if it called CPAN::Module->new;
as defined in the plugins method in App.pm
AUTHOR
Al Newkirk, <al at alnewkirk.com>
BUGS
Please report any bugs or feature requests to bug-sweetpea at rt.cpan.org
, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=SweetPea. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc SweetPea
You can also look for information at:
RT: CPAN's request tracker
AnnoCPAN: Annotated CPAN documentation
CPAN Ratings
Search CPAN
ACKNOWLEDGEMENTS
Thanks to all the developers of Mojolicious and Catalyst that inspired this.
COPYRIGHT & LICENSE
Copyright 2009 Al Newkirk.
This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.
See http://dev.perl.org/licenses/ for more information.