File Coverage

blib/lib/KelpX/Controller.pm
Criterion Covered Total %
statement 26 26 100.0
branch 8 10 80.0
condition 5 6 83.3
subroutine 6 7 85.7
pod 4 5 80.0
total 49 54 90.7


line stmt bran cond sub pod time code
1             package KelpX::Controller;
2             $KelpX::Controller::VERSION = '2.00';
3 1     1   946 use Kelp::Base;
  1         1  
  1         5  
4 1     1   228 use Carp;
  1         2  
  1         523  
5              
6             attr -context => sub { croak 'context is required for controller' };
7             attr -app => sub { $_[0]->context->app };
8              
9             sub req
10             {
11 6     6 1 594 return $_[0]->context->req;
12             }
13              
14             sub res
15             {
16 12     12 1 334 return $_[0]->context->res;
17             }
18              
19             sub add_route
20             {
21 4     4 1 26 my ($self, $route, $args) = @_;
22              
23             # borrowed from Whelk
24              
25             # make sure we have hash (same as in Kelp)
26 4 100       12 $args = {
27             to => $args,
28             } unless ref $args eq 'HASH';
29              
30             # add proper namespace to the route
31 4         11 my $base = $self->context->app->routes->base;
32 4 100 100     62 if (!ref $args->{to} && $args->{to} !~ m{^\+|#|::}) {
33 2         4 my $class = ref $self;
34 2 100       21 if ($class !~ s/^${base}:://) {
35 1         2 $class = "+$class";
36             }
37              
38 2 50       5 my $join = $class =~ m{#} ? '#' : '::';
39 2         6 $args->{to} = join $join, $class, $args->{to};
40             }
41              
42 4         11 my $location = $self->app->add_route($route, $args);
43 4   66     1958 $location->parent->dest->[0] //= ref $self; # makes sure plain subs work
44              
45 4         40 return $location;
46             }
47              
48             sub build
49       0 1   {
50             }
51              
52             sub new
53             {
54 2     2 0 3 my $class = shift;
55 2         6 my $self = $class->SUPER::new(@_);
56              
57             # make sure superclass build method won't be called
58 2 50       9 if (exists &{"${class}::build"}) {
  2         9  
59 2         7 $self->build;
60             }
61              
62 2         9 return $self;
63             }
64              
65             1;
66              
67             __END__
68              
69             =head1 NAME
70              
71             KelpX::Controller - Base custom controller for Kelp
72              
73             =head1 SYNOPSIS
74              
75             ### application's configuration
76             modules_init => {
77             Routes => {
78             base => 'My::Controller',
79             rebless => 1, # needed for historic reasons
80             }
81             },
82              
83             ### your base controller
84             package My::Controller;
85              
86             use Kelp::Base 'KelpX::Controller';
87              
88             sub build
89             {
90             my $self = shift;
91              
92             $self->add_route('/sub' => sub { ... });
93             $self->add_route('/method_name', 'this_controller_method');
94             $self->add_route('/hashref', {
95             to => 'this_controller_method',
96             });
97             }
98              
99             sub this_controller_method
100             {
101             my $self = shift;
102              
103             return 'Hello world from ' . ref $self;
104             }
105              
106             ### your main application class
107             package My::Kelp;
108              
109             attr context_obj => 'KelpX::Controller::Context';
110              
111             sub build
112             {
113             my $self = shift;
114              
115             $self->context->build_controllers;
116             }
117              
118             =head1 DESCRIPTION
119              
120             Since Kelp I<2.16> it's quite easy to introduce your own base controller class
121             instead of subclassing the main application class.
122              
123             This extension is a more modern approach to route handling, which lets you have
124             a proper hierarchy of custom classes which serve as controllers. Enabling it is
125             easy, and can be done as shown in L</SYNOPSIS>.
126              
127             The controller will be built just like a regular object the first time it's
128             used. It will not be cleared after the request, since the context object will
129             have its C<persistent_controllers> set to true by default. You may override
130             L</build> in the controller, but if you want to have it add any routes then you
131             will have to instantiate it manually using C<< $app->context->controller >>.
132              
133             =head2 Building controllers
134              
135             In order for routes defined in your controller to register, you need to build
136             the controllers. You can done it manually by calling C<<
137             $self->context->controller >> for each of your controllers, or automatically by
138             calling C<< $self->context->build_controllers >>. The latter will load all
139             modules in the namespace of your base controller using L<Module::Loader> and
140             build them. As long as all your controllers are in the same namespace under the
141             base controller, the automatic method is recommended (though it does not allow
142             for conditional disabling of the controllers).
143              
144             =head1 ATTRIBUTES
145              
146             =head2 context
147              
148             B<Required>. The app's context object.
149              
150             =head2 app
151              
152             The application object. Will be loaded from L</context>.
153              
154             =head1 METHODS
155              
156             These methods are provided for convenience, but they are not meant to fully
157             replicate the API available when not using controllers or using reblessing. For
158             this reason, this module is not a drop-in replacement for reblessing
159             controllers.
160              
161             =head2 build
162              
163             A build method, which will be called right after the controller is
164             instantiated. Similar to C<app> build method. Takes no arguments and does
165             nothing by default - it's up to you to override it.
166              
167             Since controllers inherit from one another and each route should only be built
168             once, a special check is implemented which will not run this method
169             automatically if it was not implemented in a controller class (to avoid calling
170             parent class method). Make sure B<not> to call C<SUPER> version of this method
171             if you implement it. If you need code that must be run for every controller,
172             it's recommended to override C<new>.
173              
174             =head2 add_route
175              
176             Similar to L<Kelp::Module::Routes/add_route>, but it modifies the destination
177             so that the route dispatch will run in the context of current controller.
178             Unlike core Kelp controllers, you can use simple function names or even plain
179             subroutines and they will pass the instance of your controller as their first
180             argument.
181              
182             =head2 req
183              
184             Proxy for reading the C<req> attribute from L</context>.
185              
186             =head2 res
187              
188             Proxy for reading the C<res> attribute from L</context>.
189              
190             =head1 CAVEATS
191              
192             =over
193              
194             =item
195              
196             When using this module, even subroutine based routes will be run using the
197             application's main controller (instead of application instance). Thanks to
198             this, main class will never be used as call context for route handlers, so any
199             hooks like C<before_dispatch> can be safely moved to the base controller.
200              
201             =back
202              
203             =head1 SEE ALSO
204              
205             L<Kelp>
206              
207             =head1 AUTHOR
208              
209             Bartosz Jarzyna, E<lt>bbrtj.pro@gmail.comE<gt>
210              
211             =head1 COPYRIGHT AND LICENSE
212              
213             Copyright (C) 2024 by Bartosz Jarzyna
214              
215             This library is free software; you can redistribute it and/or modify
216             it under the same terms as Perl itself.
217