=pod
=encoding utf8
=head1 NAME
Kelp::Manual::Controllers - Making your app
use
controllers
=head1 DESCRIPTION
This document describes the technical aspect of implementing controllers in
your app. By
default
, Kelp
has
no
controllers - it resolves all your routes in
the context of the main app. In other words, all routes take a instance of the
web application as a first parameter - even
if
those routes live in another
class.
Controllers lets you separate some of the route handling logic to other classes
and have your subs take the object of the correct class as the first argument.
In Kelp, there is
no
special base class
for
controllers by
default
- all
controllers must be subclasses of L<Kelp>,
unless
you want to develop your own
base controller - see L</Use different method than reblessing>.
=head2 Reblessing details
Reblessing will happen
after
request is matched to a route. Route handler
has
to be specified as class and method string, and class must be a subclass of class
configured
for
L<Kelp::Routes/base>. L<Kelp::Routes/rebless> must also be
enabled
for
that to occur.
The
default
value of C<base> field is the application class, so your
application class is by
default
your main controller class. All other
controllers must (directly or indirectly) inherit from your application class.
These methods will be automatically run on your controller object
for
each
request:
=over
=item * route handler method
=item * hooks (
if
available)
=back
No other methods will be called from your controller
unless
you call them
explicitly yourself. Application will be reblessed into a
given
controller only
once per request. If a bridge route
exists
which uses the same controller as
the regular route, the regular route will reuse the controller reblessed
for
the bridge. After the request ends, the reblessed controllers will be cleared.
=head2 Configuring controllers
=head3 Step 1: Configure the controller
It is a good practice to set up a different C<base>, so that you separate general
app code from request-handling code.
{
modules_init
=> {
Routes
=> {
rebless
=> 1,
base
=>
'MyApp::Controller'
,
}
}
}
=head3 Step 2: Create a main controller class
This step is only required
if
you've changed the C<base>.
sub
service_method {
my
$self
=
shift
;
...;
}
1;
=head3 Step 3: Create any number of controller classes
They all must inherit from your main controller class.
sub
authenticate {
my
$self
=
shift
;
...;
}
1;
=head3 Step 4: Add routes
with
shorter class names
You
no
longer have to prefix destinations
with
the base controller class name.
...
sub
build {
my
$self
=
shift
;
$self
->add_route(
'/login'
=>
'Users::authenticate'
);
}
=head2 Use different method than reblessing
While reblessing is how core Kelp deals
with
controllers, it is entirely
possible to introduce your own base controller classes. Most of the details
remain the same, like setting C<rebless> and C<base> configuration, but your
main controller class does not have to inherit from your app class.
Below is a guide on how to implement that from scratch. Alternatively,
L<KelpX::Controller> module may be used, which comes
with
pre-implemented
custom base controller and context classes.
=head3 Step 1: Custom Context object
Create a custom context object which will call C<new> on your controller
classes instead of reblessing the main app. It is also a good idea in this case
to enable C<persistent_controllers> regardless of configuration.
attr
persistent_controllers
=> !!1;
sub
build_controller {
my
(
$self
,
$class
) =
@_
;
return
$class
->new(
context
=>
$self
);
}
=head3 Step 2: Custom base controller
A base controller class
has
to implement at least C<context> to be compatible
with
core Kelp. Some specific modules might assume it's a descendant of Kelp,
which may
require
adding more methods to achieve compatibility. It may be also
a good idea to implement C<req> and C<res>
for
easier access.
attr
-context
=>
undef
;
sub
app {
my
$self
=
shift
;
return
$self
->context->app;
}
sub
req {
my
$self
=
shift
;
return
$self
->context->req;
}
sub
res {
my
$self
=
shift
;
return
$self
->context->res;
}
sub
some_route {
my
$self
=
shift
;
$self
->res->text->render(
'hello from controller'
);
}
=head3 Step 3: setting the custom context in the app
This can be done a couple different ways, but the easiest one is to change the
default
C<context_obj>, which is the class name of the context:
attr
context_obj
=>
'MyContext'
;
sub
build {
my
$self
=
shift
;
$self
->add_route(
'/'
=>
'some_route'
);
}
=head1 CAVEATS
There are some controller gotchas which come from a fact that they are not by
default
constructed like a regular object:
=head2 Main application object is shallow-cloned
before
rebless
By
default
, controllers are only temporary. Setting top-level attributes in a
controller,
for
example L<Kelp/charset>, will work
until
the request is fully
handled. After that, the controller copy will be destroyed and the changes will
not propagate back to main application. Moreover, any extra fields you set in
the controller will be lost
when
the request handling is over.
If your main app
has
no
changing state, a special configuration field
C<persistent_controllers> can be added to combat this. If it is set to a true
value, the app will be reblessed just once per controller. This means
no
changes to the main app attributes will be visible in controllers, but the
controller will be free to set and
use
all of its attributes at will. This way
your controllers will be reused indefinetly and
no
changes in app's state will
propagade to controllers.
Note that by
default
, Kelp main class
has
no
top-level state which may change
between requests so it should be pretty safe to enable this configuration as
long as you don't instantiate your controllers
before
app building is finished.
=head2 Getting a controller copy in C<build>
Controllers are never actually constructed, but instead the main app object is
cloned and reblessed into the correct class. Don't expect your
override
of
C<new> or C<build> to ever be called. No automatic controller initialization
happens in Kelp.
If you'd like to access a controller in other context than route handling -
for
example in C<build> method, allowing you to move route definitions to the
controller - you may
use
C<context> tracking object:
sub
build {
my
$self
=
shift
;
my
$controller_special
=
$self
->context->controller(
'Special'
);
$controller_special
->build;
my
$controller
=
$self
->context->controller;
$controller
->build;
}
Note that you will still have to
use
the controller name in routes even though
they live in the same class:
sub
build {
my
$self
=
shift
;
$self
->add_route(
'/my_route'
=>
'special#handler'
);
}
sub
handler { ... }
NOTE: Take extra care not to call C<build> again
if
it wasn't overridden in a
controller, as the controller will
try
to re-initialize the app, which will
surely B<result in a loop>! In addition, B<make sure to never call> C<<
$self
->SUPER::build >> in a controller.
A little trick to make sure your build don't get called more than once is to
start it
with
a guard:
sub
build {
my
$self
=
shift
;
return
unless
ref
$self
eq __PACKAGE__;
}
This way you don't have to worry about possibility of duplicated build calls
as a result of inheritance.
=head2 Getting a main application object in a controller
This may be done by similarly using C<context>:
sub
handler {
my
$controller
=
shift
;
my
$app
=
$controller
->context->app;
}
=head1 SEE ALSO
L<Kelp::Manual>
=head1 SUPPORT
=over
=back