2 use base qw(Class::Accessor::Fast Class::Data::Inheritable);
4 use UNIVERSAL::require;
9 __PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
10 __PACKAGE__->mk_accessors(
11 qw( ar params query objects model_class template_args output path
12 args action template error document_encoding content_type table)
14 __PACKAGE__->config( Maypole::Config->new() );
15 __PACKAGE__->init_done(0);
16 use Maypole::Constants;
21 my $calling_class = shift;
22 $calling_class = ref $calling_class if ref $calling_class;
27 *{ $calling_class . "::handler" } =
28 sub { Maypole::handler( $calling_class, @_ ) };
30 my $config = $calling_class->config;
31 $config->model || $config->model("Maypole::Model::CDBI");
32 $config->model->require;
33 die "Couldn't load the model class $config->model: $@" if $@;
34 $config->model->setup_database( $config, $calling_class, @_ );
35 for my $subclass ( @{ $config->classes } ) {
37 unshift @{ $subclass . "::ISA" }, $config->model;
38 $config->model->adopt($subclass)
39 if $config->model->can("adopt");
45 my $config = $class->config;
46 $config->view || $config->view("Maypole::View::TT");
47 $config->view->require;
48 die "Couldn't load the view class " . $config->view . ": $@" if $@;
49 $config->display_tables
50 || $config->display_tables( [ $class->config->tables ] );
51 $class->view_object( $class->config->view->new );
58 # See Maypole::Workflow before trying to understand this.
59 my ( $class, $req ) = @_;
60 $class->init unless $class->init_done;
61 my $r = bless { config => $class->config }, $class;
62 $r->get_request($req);
64 my $status = $r->handler_guts();
65 return $status unless $status == OK;
70 # The root of all evil
73 $r->model_class( $r->config->model->class_of( $r, $r->{table} ) );
74 my $applicable = $r->is_applicable;
75 unless ( $applicable == OK ) {
77 # It's just a plain template
78 delete $r->{model_class};
79 $r->{path} =~ s{/$}{}; # De-absolutify
80 $r->template( $r->{path} );
83 # We authenticate every request, needed for proper session management
85 eval { $status = $r->call_authenticate };
86 if ( my $error = $@ ) {
87 $status = $r->call_exception($error);
88 if ( $status != OK ) {
89 warn "caught authenticate error: $error";
90 return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
93 if ( $r->debug and $status != OK and $status != DECLINED ) {
94 $r->view_object->error( $r,
95 "Got unexpected status $status from calling authentication" );
97 return $status unless $status == OK;
99 # We run additional_data for every request
101 if ( $applicable == OK ) {
102 eval { $r->model_class->process($r) };
103 if ( my $error = $@ ) {
104 $status = $r->call_exception($error);
105 if ( $status != OK ) {
106 warn "caught model error: $error";
107 return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
111 if ( !$r->{output} ) { # You might want to do it yourself
112 eval { $status = $r->view_object->process($r) };
113 if ( my $error = $@ ) {
114 $status = $r->call_exception($error);
115 if ( $status != OK ) {
116 warn "caught view error: $error" if $r->debug;
117 return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
127 my $config = $self->config;
128 $config->ok_tables || $config->ok_tables( $config->display_tables );
129 $config->ok_tables( { map { $_ => 1 } @{ $config->ok_tables } } )
130 if ref $config->ok_tables eq "ARRAY";
131 warn "We don't have that table ($self->{table})"
133 and not $config->ok_tables->{ $self->{table} };
134 return DECLINED() unless exists $config->ok_tables->{ $self->{table} };
136 # Does the action method exist?
137 my $cv = $self->model_class->can( $self->{action} );
138 warn "We don't have that action ($self->{action})"
139 if $self->debug and not $cv;
140 return DECLINED() unless $cv;
143 $self->{method_attribs} = join " ", attributes::get($cv);
145 warn "$self->{action} not exported" if $self->debug;
147 } unless $self->{method_attribs} =~ /\bExported\b/i;
151 sub call_authenticate {
154 # Check if we have a model class
155 if ( $self->{model_class} ) {
156 return $self->model_class->authenticate($self)
157 if $self->model_class->can("authenticate");
159 return $self->authenticate($self); # Interface consistency is a Good Thing
166 # Check if we have a model class
167 if ( $self->{model_class}
168 && $self->model_class->can('exception') )
170 my $status = $self->model_class->exception( $self, $error );
171 return $status if $status == OK;
173 return $self->exception($error);
176 sub additional_data { }
178 sub authenticate { return OK }
180 sub exception { return ERROR }
184 $self->{path} ||= "frontpage";
185 my @pi = split /\//, $self->{path};
186 shift @pi while @pi and !$pi[0];
187 $self->{table} = shift @pi;
188 $self->{action} = shift @pi;
189 $self->{args} = \@pi;
194 Maypole - MVC web application framework
202 A large number of web programming tasks follow the same sort of pattern:
203 we have some data in a datasource, typically a relational database. We
204 have a bunch of templates provided by web designers. We have a number of
205 things we want to be able to do with the database - create, add, edit,
206 delete records, view records, run searches, and so on. We have a web
207 server which provides input from the user about what to do. Something in
208 the middle takes the input, grabs the relevant rows from the database,
209 performs the action, constructs a page, and spits it out.
211 Maypole aims to be the most generic and extensible "something in the
212 middle" - an MVC-based web application framework.
214 An example would help explain this best. You need to add a product
215 catalogue to a company's web site. Users need to list the products in
216 various categories, view a page on each product with its photo and
217 pricing information and so on, and there needs to be a back-end where
218 sales staff can add new lines, change prices, and delete out of date
219 records. So, you set up the database, provide some default templates
220 for the designers to customize, and then write an Apache handler like
223 package ProductDatabase;
224 use base 'Maypole::Application';
225 __PACKAGE__->set_database("dbi:mysql:products");
226 ProductDatabase->config->uri_base = "http://your.site/catalogue/";
227 ProductDatabase::Product->has_a("category" => ProductDatabase::Category);
231 my ($self, $request) = @_;
232 return OK if $request->{ar}->get_remote_host() eq "sales.yourcorp.com";
233 return OK if $request->{action} =~ /^(view|list)$/;
238 You then put the following in your Apache config:
240 <Location /catalogue>
241 SetHandler perl-script
242 PerlHandler ProductDatabase
245 And copy the templates found in F<templates/factory> into the
246 F<catalogue/factory> directory off the web root. When the designers get
247 back to you with custom templates, they are to go in
248 F<catalogue/custom>. If you need to do override templates on a
249 database-table-by-table basis, put the new template in
250 F<catalogue/I<table>>.
252 This will automatically give you C<add>, C<edit>, C<list>, C<view> and
253 C<delete> commands; for instance, a product list, go to
255 http://your.site/catalogue/product/list
257 For a full example, see the included "beer database" application.
261 There's some documentation for the workflow in L<Maypole::Workflow>,
262 but the basic idea is that a URL part like C<product/list> gets
263 translated into a call to C<ProductDatabase::Product-E<gt>list>. This
264 propagates the request with a set of objects from the database, and then
265 calls the C<list> template; first, a C<product/list> template if it
266 exists, then the C<custom/list> and finally C<factory/list>.
268 If there's another action you want the system to do, you need to either
269 subclass the model class, and configure your class slightly differently:
271 package ProductDatabase::Model;
272 use base 'Maypole::Model::CDBI';
274 sub supersearch :Exported {
275 my ($self, $request) = @_;
276 # Do stuff, get a bunch of objects back
277 $r->objects(\@objects);
278 $r->template("template_name");
281 Then your top-level application package should change the model class:
282 (Before calling C<setup>)
284 ProductDatabase->config->model("ProductDatabase::Model");
286 (The C<:Exported> attribute means that the method can be called via the
287 URL C</I<table>/supersearch/...>.)
289 Alternatively, you can put the method directly into the specific model
292 sub ProductDatabase::Product::supersearch :Exported { ... }
294 By default, the view class uses Template Toolkit as the template
295 processor, and the model class uses C<Class::DBI>; it may help you to be
296 familiar with these modules before going much further with this,
297 although I expect there to be other subclasses for other templating
298 systems and database abstraction layers as time goes on. The article at
299 C<http://www.perl.com/pub/a/2003/07/15/nocode.html> is a great
300 introduction to the process we're trying to automate.
304 You should probably not use Maypole directly. Maypole is an abstract
305 class which does not specify how to communicate with the outside world.
306 The most popular subclass of Maypole is L<Apache::MVC>, which interfaces
307 the Maypole framework to Apache mod_perl; another important one is
308 L<CGI::Maypole>. However, if you just don't care, use Maypole::Application,
309 and it will choose the right one for you.
311 If you are implementing Maypole subclasses, you need to provide at least
312 the C<parse_location> and C<send_output> methods. You may also want to
313 provide C<get_request> and C<get_template_root>. See the
314 L<Maypole::Workflow> documentation for what these are expected to do.
318 sub get_template_root { "." }
322 die "Do not use Maypole directly; use Apache::MVC or similar";
326 die "Do not use Maypole directly; use Apache::MVC or similar";
331 There's more documentation, examples, and a wiki at the Maypole web site:
333 http://maypole.simon-cozens.org/
335 L<Maypole::Application>,L<Apache::MVC>, L<CGI::Maypole>.
339 Sebastian Riedel, c<sri@oook.de>
343 Simon Cozens, C<simon@cpan.org>
347 Danijel Milicevic, Jesse Scheidlower, Jody Belka, Marcus Ramberg,
348 Mickael Joanne, Simon Flack, Veljko Vidovic and all the others who've helped.
352 You may distribute this code under the same terms as Perl itself.