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 51     51   935 use Mojo::Base 'Mojolicious::Routes::Route';
  51         145  
  51         358  
3              
4 51     51   421 use Carp qw(croak);
  51         166  
  51         2470  
5 51     51   344 use List::Util qw(first);
  51         125  
  51         3030  
6 51     51   738 use Mojo::Cache;
  51         150  
  51         348  
7 51     51   322 use Mojo::DynamicMethods;
  51         144  
  51         327  
8 51     51   719 use Mojo::Loader qw(load_class);
  51         154  
  51         2234  
9 51     51   343 use Mojo::Util qw(camelize);
  51         162  
  51         107994  
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 323 50   323 1 894 sub add_condition { $_[0]->conditions->{$_[1]} = $_[2] and return $_[0] }
18              
19             sub add_shortcut {
20 9     9 1 33 my ($self, $name, $cb) = @_;
21 9         64 $self->shortcuts->{$name} = $cb;
22 9         48 Mojo::DynamicMethods::register 'Mojolicious::Routes::Route', $self, $name, $cb;
23 9         22 return $self;
24             }
25              
26 2 50   2 1 9 sub add_type { $_[0]->types->{$_[1]} = $_[2] and return $_[0] }
27              
28             sub continue {
29 1887     1887 1 3784 my ($self, $c) = @_;
30              
31 1887         4069 my $match = $c->match;
32 1887         4269 my $stack = $match->stack;
33 1887         4326 my $position = $match->position;
34 1887 100       6241 return _render($c) unless my $field = $stack->[$position];
35              
36             # Merge captures into stash
37 1035         2942 my $stash = $c->stash;
38 1035   100     3333 @{$stash->{'mojo.captures'} //= {}}{keys %$field} = values %$field;
  1035         5432  
39 1035         3481 @$stash{keys %$field} = values %$field;
40              
41 1035         1786 my $continue;
42 1035         2310 my $last = !$stack->[++$position];
43 1035 100       2708 if (my $cb = $field->{cb}) { $continue = $self->_callback($c, $cb, $last) }
  601         1937  
44 434         1532 else { $continue = $self->_controller($c, $field, $last) }
45 1000         3494 $match->position($position);
46 1000 100 100     4829 $self->continue($c) if $last || $continue;
47             }
48              
49             sub dispatch {
50 955     955 1 2259 my ($self, $c) = @_;
51 955         3268 $self->match($c);
52 955 100       2016 @{$c->match->stack} ? $self->continue($c) : return undef;
  955         2509  
53 832         4798 return 1;
54             }
55              
56 206   66 206 1 1193 sub lookup { ($_[0]{reverse} //= $_[0]->_index)->{$_[1]} }
57              
58             sub match {
59 956     956 1 1863 my ($self, $c) = @_;
60              
61             # Path (partial path gets priority)
62 956         2695 my $req = $c->req;
63 956         2753 my $path = $c->stash->{path};
64 956 100       2527 if (defined $path) { $path = "/$path" if $path !~ m!^/! }
  64 100       367  
65 892         2489 else { $path = $req->url->path->to_route }
66              
67             # Method (HEAD will be treated as GET)
68 956         3490 my $method = uc $req->method;
69 956         2939 my $override = $req->url->query->clone->param('_method');
70 956 100 66     3946 $method = uc $override if $override && $method eq 'POST';
71 956 100       2773 $method = 'GET' if $method eq 'HEAD';
72              
73             # Check cache
74 956 100       2920 my $ws = $c->tx->is_websocket ? 1 : 0;
75 956         2758 my $match = $c->match;
76 956         3565 $match->root($self);
77 956         3023 my $cache = $self->cache;
78 956 100       5904 if (my $result = $cache->get("$method:$path:$ws")) {
79 293         1188 return $match->endpoint($result->{endpoint})->stack($result->{stack});
80             }
81              
82             # Check routes
83 663         4972 $match->find($c => {method => $method, path => $path, websocket => $ws});
84 663 100       2586 return undef unless my $route = $match->endpoint;
85 601         2965 $cache->set("$method:$path:$ws" => {endpoint => $route, stack => $match->stack});
86             }
87              
88 613     613   1881 sub _action { shift->plugins->emit_chain(around_action => @_) }
89              
90             sub _callback {
91 601     601   1591 my ($self, $c, $cb, $last) = @_;
92 601 100       2148 $c->stash->{'mojo.routed'} = 1 if $last;
93 601         1877 $c->helpers->log->trace('Routing to a callback');
94 601         2516 return _action($c->app, $c, $cb, $last);
95             }
96              
97             sub _class {
98 434     434   992 my ($self, $c, $field) = @_;
99              
100             # Application instance
101 434 100       1426 return $field->{app} if ref $field->{app};
102              
103             # Application class
104 370         652 my @classes;
105 370 100       1047 my $class = $field->{controller} ? camelize $field->{controller} : '';
106 370 100       1640 if ($field->{app}) { push @classes, $field->{app} }
  5 50       17  
    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         41 elsif ($class) { push @classes, "${_}::$class" for @{$self->namespaces} }
  23         70  
116              
117             # Try to load all classes
118 370         1728 my $log = $c->helpers->log;
119 370         1335 for my $class (@classes) {
120              
121             # Failed
122 41 100       128 next unless defined(my $found = $self->_load($class));
123 24 100       506 croak qq{Class "$class" is not a controller} unless $found;
124              
125             # Success
126 22         144 return $class->new(%$c);
127             }
128              
129             # Nothing found
130 344 100       1786 return @classes ? croak qq{Controller "$classes[-1]" does not exist} : 0;
131             }
132              
133             sub _controller {
134 434     434   1186 my ($self, $old, $field, $last) = @_;
135              
136             # Load and instantiate controller/application
137 434         713 my $new;
138 434 100       1387 unless ($new = $self->_class($old, $field)) { return defined $new }
  343         1142  
139              
140             # Application
141 86         200 my $class = ref $new;
142 86         247 my $log = $old->helpers->log;
143 86 100       566 if ($new->isa('Mojolicious')) {
    50          
144 72         375 $log->trace(qq{Routing to application "$class"});
145              
146             # Try to connect routes
147 72 50       364 if (my $sub = $new->can('routes')) {
148 72         184 my $r = $new->$sub;
149 72 100       220 $r->parent($old->match->endpoint) unless $r->parent;
150             }
151 72         284 $new->handler($old);
152 72         251 $old->stash->{'mojo.routed'} = 1;
153             }
154              
155             # Action
156             elsif (my $method = $field->{action}) {
157 14         98 $log->trace(qq{Routing to controller "$class" and action "$method"});
158              
159 14 100       79 if (my $sub = $new->can($method)) {
160 12 100       54 $old->stash->{'mojo.routed'} = 1 if $last;
161 12 100       37 return 1 if _action($old->app, $new, $sub, $last);
162             }
163              
164 2         11 else { $log->trace('Action not found in controller') }
165             }
166              
167 0         0 else { croak qq{Controller "$class" requires an action} }
168              
169 79         245 return undef;
170             }
171              
172             sub _load {
173 41     41   94 my ($self, $app) = @_;
174              
175             # Load unless already loaded
176 41 100       149 return 1 if $self->{loaded}{$app};
177 27 100       94 if (my $e = load_class $app) { ref $e ? die $e : return undef }
  17 100       102  
178              
179             # Check base classes
180 10 100   15   75 return 0 unless first { $app->isa($_) } @{$self->base_classes};
  15         140  
  10         51  
181 8         66 return $self->{loaded}{$app} = 1;
182             }
183              
184             sub _render {
185 852     852   1593 my $c = shift;
186 852         2153 my $stash = $c->stash;
187 852 100       3203 return if $stash->{'mojo.rendered'};
188 282 100 100     1137 $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