File Coverage

blib/lib/Mojolicious/Routes.pm
Criterion Covered Total %
statement 118 121 97.5
branch 67 76 88.1
condition 12 14 85.7
subroutine 21 21 100.0
pod 7 7 100.0
total 225 239 94.1


line stmt bran cond sub pod time code
1             package Mojolicious::Routes;
2 52     52   938 use Mojo::Base 'Mojolicious::Routes::Route';
  52         147  
  52         393  
3              
4 52     52   497 use Carp qw(croak);
  52         161  
  52         2555  
5 52     52   404 use List::Util qw(first);
  52         137  
  52         3088  
6 52     52   799 use Mojo::Cache;
  52         189  
  52         371  
7 52     52   353 use Mojo::DynamicMethods;
  52         178  
  52         389  
8 52     52   780 use Mojo::Loader qw(load_class);
  52         165  
  52         2170  
9 52     52   414 use Mojo::Util qw(camelize);
  52         153  
  52         116968  
10              
11             has base_classes => sub { [qw(Mojolicious::Controller Mojolicious)] };
12             has cache => sub { Mojo::Cache->new };
13             has [qw(conditions shortcuts)] => sub { {} };
14             has types => sub { {num => qr/[0-9]+/} };
15             has namespaces => sub { [] };
16              
17 326 50   326 1 1134 sub add_condition { $_[0]->conditions->{$_[1]} = $_[2] and return $_[0] }
18              
19             sub add_shortcut {
20 9     9 1 30 my ($self, $name, $cb) = @_;
21 9         47 $self->shortcuts->{$name} = $cb;
22 9         50 Mojo::DynamicMethods::register 'Mojolicious::Routes::Route', $self, $name, $cb;
23 9         30 return $self;
24             }
25              
26 2 50   2 1 13 sub add_type { $_[0]->types->{$_[1]} = $_[2] and return $_[0] }
27              
28             sub continue {
29 1893     1893 1 3603 my ($self, $c) = @_;
30              
31 1893         4266 my $match = $c->match;
32 1893         4762 my $stack = $match->stack;
33 1893         4301 my $position = $match->position;
34 1893 100       6456 return _render($c) unless my $field = $stack->[$position];
35              
36             # Merge captures into stash
37 1038         3071 my $stash = $c->stash;
38 1038   100     3432 @{$stash->{'mojo.captures'} //= {}}{keys %$field} = values %$field;
  1038         5617  
39 1038         3167 @$stash{keys %$field} = values %$field;
40              
41 1038         1864 my $continue;
42 1038         2304 my $last = !$stack->[++$position];
43 1038 100       2626 if (my $cb = $field->{cb}) { $continue = $self->_callback($c, $cb, $last) }
  601         1950  
44 437         1699 else { $continue = $self->_controller($c, $field, $last) }
45 1003         3640 $match->position($position);
46 1003 100 100     4951 $self->continue($c) if $last || $continue;
47             }
48              
49             sub dispatch {
50 962     962 1 2401 my ($self, $c) = @_;
51 962         3269 $self->match($c);
52 962 100       2034 @{$c->match->stack} ? $self->continue($c) : return undef;
  962         2664  
53 835         5161 return 1;
54             }
55              
56 206   66 206 1 1235 sub lookup { ($_[0]{reverse} //= $_[0]->_index)->{$_[1]} }
57              
58             sub match {
59 963     963 1 1983 my ($self, $c) = @_;
60              
61             # Path (partial path gets priority)
62 963         2783 my $req = $c->req;
63 963         2788 my $path = $c->stash->{path};
64 963 100       2567 if (defined $path) { $path = "/$path" if $path !~ m!^/! }
  64 100       454  
65 899         2635 else { $path = $req->url->path->to_route }
66              
67             # Method (HEAD will be treated as GET)
68 963         3551 my $method = uc $req->method;
69 963         3088 my $override = $req->url->query->clone->param('_method');
70 963 100 66     4081 $method = uc $override if $override && $method eq 'POST';
71 963 100       2645 $method = 'GET' if $method eq 'HEAD';
72              
73             # Check cache
74 963 100       2925 my $ws = $c->tx->is_websocket ? 1 : 0;
75 963         2859 my $match = $c->match;
76 963         3753 $match->root($self);
77 963         3081 my $cache = $self->cache;
78 963 100       5806 if (my $result = $cache->get("$method:$path:$ws")) {
79 293         1284 return $match->endpoint($result->{endpoint})->stack($result->{stack});
80             }
81              
82             # Check routes
83 670         5249 $match->find($c => {method => $method, path => $path, websocket => $ws});
84 670 100       2732 return undef unless my $route = $match->endpoint;
85 604         2900 $cache->set("$method:$path:$ws" => {endpoint => $route, stack => $match->stack});
86             }
87              
88 613     613   1777 sub _action { shift->plugins->emit_chain(around_action => @_) }
89              
90             sub _callback {
91 601     601   1500 my ($self, $c, $cb, $last) = @_;
92 601 100       2024 $c->stash->{'mojo.routed'} = 1 if $last;
93 601         1846 $c->helpers->log->trace('Routing to a callback');
94 601         2289 return _action($c->app, $c, $cb, $last);
95             }
96              
97             sub _class {
98 437     437   939 my ($self, $c, $field) = @_;
99              
100             # Application instance
101 437 100       1376 return $field->{app} if ref $field->{app};
102              
103             # Application class
104 373         572 my @classes;
105 373 100       1216 my $class = $field->{controller} ? camelize $field->{controller} : '';
106 373 100       1725 if ($field->{app}) { push @classes, $field->{app} }
  5 50       16  
    100          
107              
108             # Specific namespace
109             elsif (defined(my $ns = $field->{namespace})) {
110 0 0       0 croak qq{Namespace "$ns" requires a controller} unless $class;
111 0 0       0 push @classes, $ns ? "${ns}::$class" : $class;
112             }
113              
114             # All namespaces
115 23         56 elsif ($class) { push @classes, "${_}::$class" for @{$self->namespaces} }
  23         78  
116              
117             # Try to load all classes
118 373         1239 my $log = $c->helpers->log;
119 373         1412 for my $class (@classes) {
120              
121             # Failed
122 41 100       142 next unless defined(my $found = $self->_load($class));
123 24 100       560 croak qq{Class "$class" is not a controller} unless $found;
124              
125             # Success
126 22         157 return $class->new(%$c);
127             }
128              
129             # Nothing found
130 347 100       1708 return @classes ? croak qq{Controller "$classes[-1]" does not exist} : 0;
131             }
132              
133             sub _controller {
134 437     437   1226 my ($self, $old, $field, $last) = @_;
135              
136             # Load and instantiate controller/application
137 437         692 my $new;
138 437 100       1610 unless ($new = $self->_class($old, $field)) { return defined $new }
  346         1143  
139              
140             # Application
141 86         220 my $class = ref $new;
142 86         272 my $log = $old->helpers->log;
143 86 100       638 if ($new->isa('Mojolicious')) {
    50          
144 72         387 $log->trace(qq{Routing to application "$class"});
145              
146             # Try to connect routes
147 72 50       377 if (my $sub = $new->can('routes')) {
148 72         177 my $r = $new->$sub;
149 72 100       257 $r->parent($old->match->endpoint) unless $r->parent;
150             }
151 72         365 $new->handler($old);
152 72         274 $old->stash->{'mojo.routed'} = 1;
153             }
154              
155             # Action
156             elsif (my $method = $field->{action}) {
157 14         100 $log->trace(qq{Routing to controller "$class" and action "$method"});
158              
159 14 100       74 if (my $sub = $new->can($method)) {
160 12 100       49 $old->stash->{'mojo.routed'} = 1 if $last;
161 12 100       34 return 1 if _action($old->app, $new, $sub, $last);
162             }
163              
164 2         8 else { $log->trace('Action not found in controller') }
165             }
166              
167 0         0 else { croak qq{Controller "$class" requires an action} }
168              
169 79         253 return undef;
170             }
171              
172             sub _load {
173 41     41   105 my ($self, $app) = @_;
174              
175             # Load unless already loaded
176 41 100       222 return 1 if $self->{loaded}{$app};
177 27 100       98 if (my $e = load_class $app) { ref $e ? die $e : return undef }
  17 100       113  
178              
179             # Check base classes
180 10 100   15   71 return 0 unless first { $app->isa($_) } @{$self->base_classes};
  15         163  
  10         60  
181 8         64 return $self->{loaded}{$app} = 1;
182             }
183              
184             sub _render {
185 855     855   1571 my $c = shift;
186 855         2380 my $stash = $c->stash;
187 855 100       3581 return if $stash->{'mojo.rendered'};
188 285 100 100     1278 $c->render_maybe or $stash->{'mojo.routed'} or croak 'Route without action and nothing to render';
189             }
190              
191             1;
192              
193             =encoding utf8
194              
195             =head1 NAME
196              
197             Mojolicious::Routes - Always find your destination with routes
198              
199             =head1 SYNOPSIS
200              
201             use Mojolicious::Routes;
202              
203             # Simple route
204             my $r = Mojolicious::Routes->new;
205             $r->any('/')->to(controller => 'blog', action => 'welcome');
206              
207             # More advanced routes
208             my $blog = $r->under('/blog');
209             $blog->get('/list')->to('blog#list');
210             $blog->get('/:id' => [id => qr/\d+/])->to('blog#show', id => 23);
211             $blog->patch(sub ($c) { $c->render(text => 'Go away!', status => 405) });
212              
213             =head1 DESCRIPTION
214              
215             L is the core of the L web framework.
216              
217             See L for more.
218              
219             =head1 TYPES
220              
221             These placeholder types are available by default.
222              
223             =head2 num
224              
225             $r->get('/article/');
226              
227             Placeholder value needs to be a non-fractional number, similar to the regular expression C<([0-9]+)>.
228              
229             =head1 ATTRIBUTES
230              
231             L inherits all attributes from L and implements the following new
232             ones.
233              
234             =head2 base_classes
235              
236             my $classes = $r->base_classes;
237             $r = $r->base_classes(['MyApp::Controller']);
238              
239             Base classes used to identify controllers, defaults to L and L.
240              
241             =head2 cache
242              
243             my $cache = $r->cache;
244             $r = $r->cache(Mojo::Cache->new);
245              
246             Routing cache, defaults to a L object.
247              
248             =head2 conditions
249              
250             my $conditions = $r->conditions;
251             $r = $r->conditions({foo => sub {...}});
252              
253             Contains all available conditions.
254              
255             =head2 namespaces
256              
257             my $namespaces = $r->namespaces;
258             $r = $r->namespaces(['MyApp::Controller', 'MyApp']);
259              
260             Namespaces to load controllers from.
261              
262             # Add another namespace to load controllers from
263             push @{$r->namespaces}, 'MyApp::MyController';
264              
265             =head2 shortcuts
266              
267             my $shortcuts = $r->shortcuts;
268             $r = $r->shortcuts({foo => sub {...}});
269              
270             Contains all available shortcuts.
271              
272             =head2 types
273              
274             my $types = $r->types;
275             $r = $r->types({lower => qr/[a-z]+/});
276              
277             Registered placeholder types, by default only L is already defined.
278              
279             =head1 METHODS
280              
281             L inherits all methods from L and implements the following new ones.
282              
283             =head2 add_condition
284              
285             $r = $r->add_condition(foo => sub ($route, $c, $captures, $arg) {...});
286              
287             Register a condition.
288              
289             $r->add_condition(foo => sub ($route, $c, $captures, $arg) {
290             ...
291             return 1;
292             });
293              
294             =head2 add_shortcut
295              
296             $r = $r->add_shortcut(foo => sub ($route, @args) {...});
297              
298             Register a shortcut.
299              
300             $r->add_shortcut(foo => sub ($route, @args) {...});
301              
302             =head2 add_type
303              
304             $r = $r->add_type(foo => qr/\w+/);
305             $r = $r->add_type(foo => ['bar', 'baz']);
306              
307             Register a placeholder type.
308              
309             $r->add_type(lower => qr/[a-z]+/);
310              
311             =head2 continue
312              
313             $r->continue(Mojolicious::Controller->new);
314              
315             Continue dispatch chain and emit the hook L for every action.
316              
317             =head2 dispatch
318              
319             my $bool = $r->dispatch(Mojolicious::Controller->new);
320              
321             Match routes with L and dispatch with L.
322              
323             =head2 lookup
324              
325             my $route = $r->lookup('foo');
326              
327             Find route by name with L and cache all results for future lookups.
328              
329             =head2 match
330              
331             $r->match(Mojolicious::Controller->new);
332              
333             Match routes with L.
334              
335             =head1 SEE ALSO
336              
337             L, L, L.
338              
339             =cut