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   912 use Mojo::Base 'Mojolicious::Routes::Route';
  55         136  
  55         491  
3              
4 55     55   598 use Carp qw(croak);
  55         141  
  55         4067  
5 55     55   420 use List::Util qw(first);
  55         134  
  55         4186  
6 55     55   730 use Mojo::Cache;
  55         132  
  55         479  
7 55     55   322 use Mojo::DynamicMethods;
  55         152  
  55         506  
8 55     55   751 use Mojo::Loader qw(load_class);
  55         208  
  55         3219  
9 55     55   341 use Mojo::Util qw(camelize);
  55         187  
  55         162170  
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 1191 sub add_condition { $_[0]->conditions->{$_[1]} = $_[2] and return $_[0] }
18              
19             sub add_shortcut {
20 9     9 1 29 my ($self, $name, $cb) = @_;
21 9         44 $self->shortcuts->{$name} = $cb;
22 9         42 Mojo::DynamicMethods::register 'Mojolicious::Routes::Route', $self, $name, $cb;
23 9         25 return $self;
24             }
25              
26 2 50   2 1 11 sub add_type { $_[0]->types->{$_[1]} = $_[2] and return $_[0] }
27              
28             sub continue {
29 2025     2025 1 4688 my ($self, $c) = @_;
30              
31 2025         5159 my $match = $c->match;
32 2025         5349 my $stack = $match->stack;
33 2025         5571 my $position = $match->position;
34 2025 100       7812 return _render($c) unless my $field = $stack->[$position];
35              
36             # Merge captures into stash
37 1104         3855 my $stash = $c->stash;
38 1104   100     4031 @{$stash->{'mojo.captures'} //= {}}{keys %$field} = values %$field;
  1104         9741  
39 1104         4090 @$stash{keys %$field} = values %$field;
40              
41 1104         2403 my $continue;
42 1104         2991 my $last = !$stack->[++$position];
43 1104 100       3467 if (my $cb = $field->{cb}) { $continue = $self->_callback($c, $cb, $last) }
  666         4981  
44 438         2009 else { $continue = $self->_controller($c, $field, $last) }
45 1069         5391 $match->position($position);
46 1069 100 100     6374 $self->continue($c) if $last || $continue;
47             }
48              
49             sub dispatch {
50 1029     1029 1 3046 my ($self, $c) = @_;
51 1029         5332 $self->match($c);
52 1029 100       2476 @{$c->match->stack} ? $self->continue($c) : return undef;
  1029         3451  
53 901         7269 return 1;
54             }
55              
56 206   66 206 1 1531 sub lookup { ($_[0]{reverse} //= $_[0]->_index)->{$_[1]} }
57              
58             sub match {
59 1030     1030 1 2500 my ($self, $c) = @_;
60              
61             # Path (partial path gets priority)
62 1030         4176 my $req = $c->req;
63 1030         3469 my $path = $c->stash->{path};
64 1030 100       2988 if (defined $path) { $path = "/$path" if $path !~ m!^/! }
  64 100       306  
65 966         3513 else { $path = $req->url->path->to_route }
66              
67             # Method (HEAD will be treated as GET)
68 1030         4436 my $method = uc $req->method;
69 1030         3491 my $override = $req->url->query->clone->param('_method');
70 1030 100 66     5536 $method = uc $override if $override && $method eq 'POST';
71 1030 100       3688 $method = 'GET' if $method eq 'HEAD';
72              
73             # Check cache
74 1030 100       3925 my $ws = $c->tx->is_websocket ? 1 : 0;
75 1030         4066 my $match = $c->match;
76 1030         4896 $match->root($self);
77 1030         4601 my $cache = $self->cache;
78 1030 100       9280 if (my $result = $cache->get("$method:$path:$ws")) {
79 345         1612 return $match->endpoint($result->{endpoint})->stack($result->{stack});
80             }
81              
82             # Check routes
83 685         7087 $match->find($c => {method => $method, path => $path, websocket => $ws});
84 685 100       3265 return undef unless my $route = $match->endpoint;
85 618         3979 $cache->set("$method:$path:$ws" => {endpoint => $route, stack => $match->stack});
86             }
87              
88 678     678   2253 sub _action { shift->plugins->emit_chain(around_action => @_) }
89              
90             sub _callback {
91 666     666   2020 my ($self, $c, $cb, $last) = @_;
92 666 100       3657 $c->stash->{'mojo.routed'} = 1 if $last;
93 666         2532 $c->helpers->log->trace('Routing to a callback');
94 666         2543 return _action($c->app, $c, $cb, $last);
95             }
96              
97             sub _class {
98 438     438   1153 my ($self, $c, $field) = @_;
99              
100             # Application instance
101 438 100       1639 return $field->{app} if ref $field->{app};
102              
103             # Application class
104 374         714 my @classes;
105 374 100       1315 my $class = $field->{controller} ? camelize $field->{controller} : '';
106 374 100       3041 if ($field->{app}) { push @classes, $field->{app} }
  5 50       11  
    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         42 elsif ($class) { push @classes, "${_}::$class" for @{$self->namespaces} }
  23         91  
116              
117             # Try to load all classes
118 374         1493 my $log = $c->helpers->log;
119 374         1457 for my $class (@classes) {
120              
121             # Failed
122 41 100       155 next unless defined(my $found = $self->_load($class));
123 24 100       700 croak qq{Class "$class" is not a controller} unless $found;
124              
125             # Success
126 22         146 return $class->new(%$c);
127             }
128              
129             # Nothing found
130 348 100       2076 return @classes ? croak qq{Controller "$classes[-1]" does not exist} : 0;
131             }
132              
133             sub _controller {
134 438     438   1329 my ($self, $old, $field, $last) = @_;
135              
136             # Load and instantiate controller/application
137 438         858 my $new;
138 438 100       1739 unless ($new = $self->_class($old, $field)) { return defined $new }
  347         1148  
139              
140             # Application
141 86         234 my $class = ref $new;
142 86         288 my $log = $old->helpers->log;
143 86 100       756 if ($new->isa('Mojolicious')) {
    50          
144 72         422 $log->trace(qq{Routing to application "$class"});
145              
146             # Try to connect routes
147 72 50       419 if (my $sub = $new->can('routes')) {
148 72         181 my $r = $new->$sub;
149 72 100       260 $r->parent($old->match->endpoint) unless $r->parent;
150             }
151 72         344 $new->handler($old);
152 72         314 $old->stash->{'mojo.routed'} = 1;
153             }
154              
155             # Action
156             elsif (my $method = $field->{action}) {
157 14         86 $log->trace(qq{Routing to controller "$class" and action "$method"});
158              
159 14 100       67 if (my $sub = $new->can($method)) {
160 12 100       49 $old->stash->{'mojo.routed'} = 1 if $last;
161 12 100       39 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         275 return undef;
170             }
171              
172             sub _load {
173 41     41   160 my ($self, $app) = @_;
174              
175             # Load unless already loaded
176 41 100       195 return 1 if $self->{loaded}{$app};
177 27 100       119 if (my $e = load_class $app) { ref $e ? die $e : return undef }
  17 100       125  
178              
179             # Check base classes
180 10 100   15   55 return 0 unless first { $app->isa($_) } @{$self->base_classes};
  15         165  
  10         66  
181 8         52 return $self->{loaded}{$app} = 1;
182             }
183              
184             sub _render {
185 921     921   1948 my $c = shift;
186 921         3027 my $stash = $c->stash;
187 921 100       4223 return if $stash->{'mojo.rendered'};
188 286 100 100     1205 $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