2 use base qw(Class::Accessor Class::Data::Inheritable);
4 use UNIVERSAL::require;
8 __PACKAGE__->mk_classdata($_) for qw( config init_done view_object );
9 __PACKAGE__->mk_accessors(
10 qw( ar params query objects model_class
11 args action template )
13 __PACKAGE__->config( {} );
14 __PACKAGE__->init_done(0);
15 use Maypole::Constants;
20 my $calling_class = shift;
21 $calling_class = ref $calling_class if ref $calling_class;
26 *{ $calling_class . "::handler" } =
27 sub { Maypole::handler( $calling_class, @_ ) };
29 my $config = $calling_class->config;
30 $config->{model} ||= "Maypole::Model::CDBI";
31 $config->{model}->require;
32 die "Couldn't load the model class $config->{model}: $@" if $@;
33 $config->{model}->setup_database( $config, $calling_class, @_ );
34 for my $subclass ( @{ $config->{classes} } ) {
36 unshift @{ $subclass . "::ISA" }, $config->{model};
37 $config->{model}->adopt($subclass)
38 if $config->{model}->can("adopt");
44 my $config = $class->config;
45 $config->{view} ||= "Maypole::View::TT";
46 $config->{view}->require;
47 die "Couldn't load the view class $config->{view}: $@" if $@;
48 $config->{display_tables} ||= [ @{ $class->config->{tables} } ];
49 $class->view_object( $class->config->{view}->new );
56 # See Maypole::Workflow before trying to understand this.
57 my ( $class, $req ) = @_;
58 $class->init unless $class->init_done;
59 my $r = bless { config => $class->config }, $class;
60 $r->get_request($req);
62 my $status = $r->handler_guts();
63 return $status unless $status == OK;
70 $r->model_class( $r->config->{model}->class_of( $r, $r->{table} ) );
71 my $applicable = $r->is_applicable;
72 unless ( $applicable == OK ) {
74 # It's just a plain template
75 delete $r->{model_class};
76 $r->{path} =~ s{/$}{}; # De-absolutify
77 $r->template( $r->{path} );
80 # We authenticate every request, needed for proper session management
81 my $status = $r->call_authenticate;
82 if ( $r->debug and $status != OK and $status != DECLINED ) {
83 $r->view_object->error( $r,
84 "Got unexpected status $status from calling authentication" );
86 return $status unless $status == OK;
88 # We run additional_data for every request
90 if ( $applicable == OK ) {
91 eval { $r->model_class->process($r) };
92 if ( my $error = $@ ) {
93 $status = $r->call_exception($error);
94 if ( $status != OK ) {
95 warn "caught model error: $error";
96 return $r->debug ? $r->view_object->error( $r, $error ) : ERROR;
100 if ( !$r->{output} ) { # You might want to do it yourself
101 return $r->view_object->process($r);
108 my $config = $self->config;
109 $config->{ok_tables} ||= $config->{display_tables};
110 $config->{ok_tables} = { map { $_ => 1 } @{ $config->{ok_tables} } }
111 if ref $config->{ok_tables} eq "ARRAY";
112 warn "We don't have that table ($self->{table})"
114 and not $config->{ok_tables}{ $self->{table} };
115 return DECLINED() unless exists $config->{ok_tables}{ $self->{table} };
117 # Does the action method exist?
118 my $cv = $self->model_class->can( $self->{action} );
119 warn "We don't have that action ($self->{action})"
120 if $self->debug and not $cv;
121 return DECLINED() unless $cv;
124 $self->{method_attribs} = join " ", attributes::get($cv);
126 warn "$self->{action} not exported" if $self->debug;
128 } unless $self->{method_attribs} =~ /\bExported\b/i;
132 sub call_authenticate {
135 # Check if we have a model class
136 if ( $self->{model_class} ) {
137 return $self->model_class->authenticate($self)
138 if $self->model_class->can("authenticate");
140 return $self->authenticate($self); # Interface consistency is a Good Thing
147 # Check if we have a model class
148 if ( $self->{model_class}
149 && $self->model_class->can('exception') )
151 my $status = $self->model_class->exception( $self, $error );
152 return $status if $status == OK;
154 return $self->exception($error);
157 sub additional_data { }
159 sub authenticate { return OK }
161 sub exception { return ERROR }
165 $self->{path} ||= "frontpage";
166 my @pi = split /\//, $self->{path};
167 shift @pi while @pi and !$pi[0];
168 $self->{table} = shift @pi;
169 $self->{action} = shift @pi;
170 $self->{args} = \@pi;
175 Maypole - MVC web application framework
183 A large number of web programming tasks follow the same sort of pattern:
184 we have some data in a datasource, typically a relational database. We
185 have a bunch of templates provided by web designers. We have a number of
186 things we want to be able to do with the database - create, add, edit,
187 delete records, view records, run searches, and so on. We have a web
188 server which provides input from the user about what to do. Something in
189 the middle takes the input, grabs the relevant rows from the database,
190 performs the action, constructs a page, and spits it out.
192 Maypole aims to be the most generic and extensible "something in the
193 middle" - an MVC-based web application framework.
195 An example would help explain this best. You need to add a product
196 catalogue to a company's web site. Users need to list the products in
197 various categories, view a page on each product with its photo and
198 pricing information and so on, and there needs to be a back-end where
199 sales staff can add new lines, change prices, and delete out of date
200 records. So, you set up the database, provide some default templates
201 for the designers to customize, and then write an Apache handler like
204 package ProductDatabase;
205 use base 'Apache::MVC';
206 __PACKAGE__->set_database("dbi:mysql:products");
207 ProductDatabase->config->{uri_base} = "http://your.site/catalogue/";
208 ProductDatabase::Product->has_a("category" => ProductDatabase::Category);
212 my ($self, $request) = @_;
213 return OK if $request->{ar}->get_remote_host() eq "sales.yourcorp.com";
214 return OK if $request->{action} =~ /^(view|list)$/;
219 You then put the following in your Apache config:
221 <Location /catalogue>
222 SetHandler perl-script
223 PerlHandler ProductDatabase
226 And copy the templates found in F<templates/factory> into the
227 F<catalogue/factory> directory off the web root. When the designers get
228 back to you with custom templates, they are to go in
229 F<catalogue/custom>. If you need to do override templates on a
230 database-table-by-table basis, put the new template in
231 F<catalogue/I<table>>.
233 This will automatically give you C<add>, C<edit>, C<list>, C<view> and
234 C<delete> commands; for instance, a product list, go to
236 http://your.site/catalogue/product/list
238 For a full example, see the included "beer database" application.
242 There's some documentation for the workflow in L<Maypole::Workflow>,
243 but the basic idea is that a URL part like C<product/list> gets
244 translated into a call to C<ProductDatabase::Product-E<gt>list>. This
245 propagates the request with a set of objects from the database, and then
246 calls the C<list> template; first, a C<product/list> template if it
247 exists, then the C<custom/list> and finally C<factory/list>.
249 If there's another action you want the system to do, you need to either
250 subclass the model class, and configure your class slightly differently:
252 package ProductDatabase::Model;
253 use base 'Maypole::Model::CDBI';
255 sub supersearch :Exported {
256 my ($self, $request) = @_;
257 # Do stuff, get a bunch of objects back
258 $r->objects(\@objects);
259 $r->template("template_name");
262 Then your top-level application package should change the model class:
263 (Before calling C<setup>)
265 ProductDatabase->config->{model} = "ProductDatabase::Model";
267 (The C<:Exported> attribute means that the method can be called via the
268 URL C</I<table>/supersearch/...>.)
270 Alternatively, you can put the method directly into the specific model
273 sub ProductDatabase::Product::supersearch :Exported { ... }
275 By default, the view class uses Template Toolkit as the template
276 processor, and the model class uses C<Class::DBI>; it may help you to be
277 familiar with these modules before going much further with this,
278 although I expect there to be other subclasses for other templating
279 systems and database abstraction layers as time goes on. The article at
280 C<http://www.perl.com/pub/a/2003/07/15/nocode.html> is a great
281 introduction to the process we're trying to automate.
285 You should probably not use Maypole directly. Maypole is an abstract
286 class which does not specify how to communicate with the outside world.
287 The most popular subclass of Maypole is L<Apache::MVC>, which interfaces
288 the Maypole framework to Apache mod_perl; another important one is
291 If you are implementing Maypole subclasses, you need to provide at least
292 the C<parse_location> and C<send_output> methods. You may also want to
293 provide C<get_request> and C<get_template_root>. See the
294 L<Maypole::Workflow> documentation for what these are expected to do.
298 sub get_template_root { "." }
302 die "Do not use Maypole directly; use Apache::MVC or similar";
306 die "Do not use Maypole directly; use Apache::MVC or similar";
311 There's more documentation, examples, and a wiki at the Maypole web site:
313 http://maypole.simon-cozens.org/
315 L<Apache::MVC>, L<CGI::Maypole>.
319 Sebastian Riedel, c<sri@oook.de>
323 Simon Cozens, C<simon@cpan.org>
327 Jesse Scheidlower, Jody Belka, Markus Ramberg, Mickael Joanne, Simon Flack and all the others who've helped.
331 You may distribute this code under the same terms as Perl itself.