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 55     55   741 use Mojo::Base 'Mojolicious::Routes::Route';
  55         110  
  55         321  
3              
4 55     55   397 use Carp qw(croak);
  55         106  
  55         2459  
5 55     55   245 use List::Util qw(first);
  55         84  
  55         2666  
6 55     55   536 use Mojo::Cache;
  55         97  
  55         316  
7 55     55   197 use Mojo::DynamicMethods;
  55         90  
  55         356  
8 55     55   562 use Mojo::Loader qw(load_class);
  55         134  
  55         1943  
9 55     55   226 use Mojo::Util qw(camelize);
  55         82  
  55         103752  
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 338 50   338 1 771 sub add_condition { $_[0]->conditions->{$_[1]} = $_[2] and return $_[0] }
18              
19             sub add_shortcut {
20 9     9 1 23 my ($self, $name, $cb) = @_;
21 9         36 $self->shortcuts->{$name} = $cb;
22 9         31 Mojo::DynamicMethods::register 'Mojolicious::Routes::Route', $self, $name, $cb;
23 9         19 return $self;
24             }
25              
26 2 50   2 1 7 sub add_type { $_[0]->types->{$_[1]} = $_[2] and return $_[0] }
27              
28             sub continue {
29 2033     2033 1 3122 my ($self, $c) = @_;
30              
31 2033         3311 my $match = $c->match;
32 2033         3424 my $stack = $match->stack;
33 2033         3641 my $position = $match->position;
34 2033 100       5360 return _render($c) unless my $field = $stack->[$position];
35              
36             # Merge captures into stash
37 1108         2656 my $stash = $c->stash;
38 1108   100     2669 @{$stash->{'mojo.captures'} //= {}}{keys %$field} = values %$field;
  1108         4669  
39 1108         2809 @$stash{keys %$field} = values %$field;
40              
41 1108         1451 my $continue;
42 1108         2052 my $last = !$stack->[++$position];
43 1108 100       2379 if (my $cb = $field->{cb}) { $continue = $self->_callback($c, $cb, $last) }
  667         1862  
44 441         1248 else { $continue = $self->_controller($c, $field, $last) }
45 1073         3115 $match->position($position);
46 1073 100 100     4361 $self->continue($c) if $last || $continue;
47             }
48              
49             sub dispatch {
50 1033     1033 1 1826 my ($self, $c) = @_;
51 1033         3346 $self->match($c);
52 1033 100       1716 @{$c->match->stack} ? $self->continue($c) : return undef;
  1033         2255  
53 905         4864 return 1;
54             }
55              
56 206   66 206 1 1007 sub lookup { ($_[0]{reverse} //= $_[0]->_index)->{$_[1]} }
57              
58             sub match {
59 1034     1034 1 1774 my ($self, $c) = @_;
60              
61             # Path (partial path gets priority)
62 1034         2345 my $req = $c->req;
63 1034         2202 my $path = $c->stash->{path};
64 1034 100       2185 if (defined $path) { $path = "/$path" if $path !~ m!^/! }
  64 100       264  
65 970         2268 else { $path = $req->url->path->to_route }
66              
67             # Method (HEAD will be treated as GET)
68 1034         2869 my $method = uc $req->method;
69 1034         2369 my $override = $req->url->query->clone->param('_method');
70 1034 100 66     3575 $method = uc $override if $override && $method eq 'POST';
71 1034 100       2263 $method = 'GET' if $method eq 'HEAD';
72              
73             # Check cache
74 1034 100       2472 my $ws = $c->tx->is_websocket ? 1 : 0;
75 1034         2316 my $match = $c->match;
76 1034         3167 $match->root($self);
77 1034         2933 my $cache = $self->cache;
78 1034 100       5369 if (my $result = $cache->get("$method:$path:$ws")) {
79 346         1164 return $match->endpoint($result->{endpoint})->stack($result->{stack});
80             }
81              
82             # Check routes
83 688         4429 $match->find($c => {method => $method, path => $path, websocket => $ws});
84 688 100       2074 return undef unless my $route = $match->endpoint;
85 621         2023 $cache->set("$method:$path:$ws" => {endpoint => $route, stack => $match->stack});
86             }
87              
88 679     679   1718 sub _action { shift->plugins->emit_chain(around_action => @_) }
89              
90             sub _callback {
91 667     667   1482 my ($self, $c, $cb, $last) = @_;
92 667 100       1833 $c->stash->{'mojo.routed'} = 1 if $last;
93 667         1671 $c->helpers->log->trace('Routing to a callback');
94 667         1739 return _action($c->app, $c, $cb, $last);
95             }
96              
97             sub _class {
98 441     441   787 my ($self, $c, $field) = @_;
99              
100             # Application instance
101 441 100       1090 return $field->{app} if ref $field->{app};
102              
103             # Application class
104 377         790 my @classes;
105 377 100       900 my $class = $field->{controller} ? camelize $field->{controller} : '';
106 377 100       1472 if ($field->{app}) { push @classes, $field->{app} }
  5 50       13  
    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         37 elsif ($class) { push @classes, "${_}::$class" for @{$self->namespaces} }
  23         69  
116              
117             # Try to load all classes
118 377         909 my $log = $c->helpers->log;
119 377         937 for my $class (@classes) {
120              
121             # Failed
122 41 100       100 next unless defined(my $found = $self->_load($class));
123 24 100       475 croak qq{Class "$class" is not a controller} unless $found;
124              
125             # Success
126 22         142 return $class->new(%$c);
127             }
128              
129             # Nothing found
130 351 100       1362 return @classes ? croak qq{Controller "$classes[-1]" does not exist} : 0;
131             }
132              
133             sub _controller {
134 441     441   941 my ($self, $old, $field, $last) = @_;
135              
136             # Load and instantiate controller/application
137 441         547 my $new;
138 441 100       1132 unless ($new = $self->_class($old, $field)) { return defined $new }
  350         810  
139              
140             # Application
141 86         162 my $class = ref $new;
142 86         220 my $log = $old->helpers->log;
143 86 100       514 if ($new->isa('Mojolicious')) {
    50          
144 72         344 $log->trace(qq{Routing to application "$class"});
145              
146             # Try to connect routes
147 72 50       371 if (my $sub = $new->can('routes')) {
148 72         170 my $r = $new->$sub;
149 72 100       208 $r->parent($old->match->endpoint) unless $r->parent;
150             }
151 72         250 $new->handler($old);
152 72         183 $old->stash->{'mojo.routed'} = 1;
153             }
154              
155             # Action
156             elsif (my $method = $field->{action}) {
157 14         61 $log->trace(qq{Routing to controller "$class" and action "$method"});
158              
159 14 100       51 if (my $sub = $new->can($method)) {
160 12 100       32 $old->stash->{'mojo.routed'} = 1 if $last;
161 12 100       25 return 1 if _action($old->app, $new, $sub, $last);
162             }
163              
164 2         6 else { $log->trace('Action not found in controller') }
165             }
166              
167 0         0 else { croak qq{Controller "$class" requires an action} }
168              
169 79         190 return undef;
170             }
171              
172             sub _load {
173 41     41   73 my ($self, $app) = @_;
174              
175             # Load unless already loaded
176 41 100       124 return 1 if $self->{loaded}{$app};
177 27 100       83 if (my $e = load_class $app) { ref $e ? die $e : return undef }
  17 100       92  
178              
179             # Check base classes
180 10 100   15   41 return 0 unless first { $app->isa($_) } @{$self->base_classes};
  15         153  
  10         33  
181 8         48 return $self->{loaded}{$app} = 1;
182             }
183              
184             sub _render {
185 925     925   1428 my $c = shift;
186 925         1936 my $stash = $c->stash;
187 925 100       2855 return if $stash->{'mojo.rendered'};
188 289 100 100     795 $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