Add POD for setup_actions method in dispatcher, slight whitespace cleanups. Re-write...
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Dispatcher.pm
CommitLineData
68a748b9 1package Catalyst::Dispatcher;
1abd6db7 2
059c085b 3use Moose;
068c0898 4use Class::MOP;
531f1ab6 5with 'MooseX::Emulate::Class::Accessor::Fast';
059c085b 6
a2f2cde9 7use Catalyst::Exception;
f05af9ba 8use Catalyst::Utils;
fbcc39ad 9use Catalyst::Action;
b7aebc12 10use Catalyst::ActionContainer;
b96f127f 11use Catalyst::DispatchType::Default;
bcccee4e 12use Catalyst::DispatchType::Index;
39fc2ce1 13use Catalyst::Utils;
87b85407 14use Text::SimpleTable;
1abd6db7 15use Tree::Simple;
16use Tree::Simple::Visitor::FindByPath;
17
c41cfce3 18# Refactoring note:
19# do these belong as package vars or should we build these via a builder method?
20# See Catalyst-Plugin-Server for them being added to, which should be much less ugly.
21
6d030e6f 22# Preload these action types
61a9002d 23our @PRELOAD = qw/Index Path Regex/;
1abd6db7 24
2d1d8f91 25# Postload these action types
61a9002d 26our @POSTLOAD = qw/Default/;
2d1d8f91 27
c41cfce3 28# Note - see back-compat methods at end of file.
29has _tree => (is => 'rw');
30has _dispatch_types => (is => 'rw', default => sub { [] }, required => 1, lazy => 1);
31has _registered_dispatch_types => (is => 'rw', default => sub { {} }, required => 1, lazy => 1);
32has _method_action_class => (is => 'rw', default => 'Catalyst::Action');
33has _action_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
34has _container_hash => (is => 'rw', required => 1, lazy => 1, default => sub { {} });
8c80e4f8 35
5fb12dbb 36has preload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@PRELOAD] });
37has postload_dispatch_types => (is => 'rw', required => 1, lazy => 1, default => sub { [@POSTLOAD] });
059c085b 38
083ee5d9 39# Wrap accessors so you can assign a list and it will capture a list ref.
40around qw/preload_dispatch_types postload_dispatch_types/ => sub {
41 my $orig = shift;
42 my $self = shift;
43 return $self->$orig([@_]) if (scalar @_ && ref $_[0] ne 'ARRAY');
44 return $self->$orig(@_);
45};
46
059c085b 47no Moose;
48
1abd6db7 49=head1 NAME
50
9c053379 51Catalyst::Dispatcher - The Catalyst Dispatcher
1abd6db7 52
53=head1 SYNOPSIS
54
55See L<Catalyst>.
56
57=head1 DESCRIPTION
58
4ab87e27 59This is the class that maps public urls to actions in your Catalyst
60application based on the attributes you set.
61
1abd6db7 62=head1 METHODS
63
ac5c933b 64=head2 new
4ab87e27 65
66Construct a new dispatcher.
67
e7bb8d33 68=cut
69
059c085b 70sub BUILD {
71 my ($self, $params) = @_;
9e81ba44 72
068c0898 73 my $container =
059c085b 74 Catalyst::ActionContainer->new( { part => '/', actions => {} } );
a13e21ab 75
c41cfce3 76 $self->_tree( Tree::Simple->new( $container, Tree::Simple->ROOT ) );
e7bb8d33 77}
78
79=head2 $self->preload_dispatch_types
80
81An arrayref of pre-loaded dispatchtype classes
82
83Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
84To use a custom class outside the regular C<Catalyst> namespace, prefix
85it with a C<+>, like so:
86
87 +My::Dispatch::Type
88
89=head2 $self->postload_dispatch_types
90
91An arrayref of post-loaded dispatchtype classes
92
93Entries are considered to be available as C<Catalyst::DispatchType::CLASS>
94To use a custom class outside the regular C<Catalyst> namespace, prefix
95it with a C<+>, like so:
96
97 +My::Dispatch::Type
98
b5ecfcf0 99=head2 $self->dispatch($c)
1abd6db7 100
4ab87e27 101Delegate the dispatch to the action that matched the url, or return a
102message about unknown resource
103
1abd6db7 104=cut
105
106sub dispatch {
fbcc39ad 107 my ( $self, $c ) = @_;
e63bdf38 108 if ( my $action = $c->action ) {
109 $c->forward( join( '/', '', $action->namespace, '_DISPATCH' ) );
fbcc39ad 110 }
fbcc39ad 111 else {
1abd6db7 112 my $path = $c->req->path;
113 my $error = $path
114 ? qq/Unknown resource "$path"/
115 : "No default action defined";
116 $c->log->error($error) if $c->debug;
117 $c->error($error);
118 }
119}
120
2f381252 121# $self->_command2action( $c, $command [, \@arguments ] )
122# Search for an action, from the command and returns C<($action, $args)> on
123# success. Returns C<(0)> on error.
1abd6db7 124
2f381252 125sub _command2action {
e72f8f51 126 my ( $self, $c, $command, @extra_params ) = @_;
99fe1710 127
1abd6db7 128 unless ($command) {
2f381252 129 $c->log->debug('Nothing to go to') if $c->debug;
1abd6db7 130 return 0;
131 }
99fe1710 132
e72f8f51 133 my @args;
068c0898 134
e72f8f51 135 if ( ref( $extra_params[-1] ) eq 'ARRAY' ) {
136 @args = @{ pop @extra_params }
137 } else {
2f381252 138 # this is a copy, it may take some abuse from
139 # ->_invoke_as_path if the path had trailing parts
e72f8f51 140 @args = @{ $c->request->arguments };
141 }
142
143 my $action;
144
2f381252 145 # go to a string path ("/foo/bar/gorch")
e31b525c 146 # or action object
7e95ba12 147 if (blessed($command) && $command->isa('Catalyst::Action')) {
e31b525c 148 $action = $command;
149 }
150 else {
151 $action = $self->_invoke_as_path( $c, "$command", \@args );
152 }
99fe1710 153
2f381252 154 # go to a component ( "MyApp::*::Foo" or $c->component("...")
155 # - a path or an object)
e72f8f51 156 unless ($action) {
157 my $method = @extra_params ? $extra_params[0] : "process";
158 $action = $self->_invoke_as_component( $c, $command, $method );
159 }
99fe1710 160
2f381252 161 return $action, \@args;
162}
163
ae0e35ee 164=head2 $self->visit( $c, $command [, \@arguments ] )
2f381252 165
166Documented in L<Catalyst>
167
168=cut
169
ae0e35ee 170sub visit {
2f381252 171 my $self = shift;
ae0e35ee 172 $self->_do_visit('visit', @_);
173}
174
175sub _do_visit {
176 my $self = shift;
177 my $opname = shift;
2f381252 178 my ( $c, $command ) = @_;
179 my ( $action, $args ) = $self->_command2action(@_);
ae0e35ee 180 my $error = qq/Couldn't $opname("$command"): /;
2f381252 181
ae0e35ee 182 if (!$action) {
3ea37672 183 $error .= qq/Couldn't $opname to command "$command": /
184 .qq/Invalid action or component./;
ae0e35ee 185 }
186 elsif (!defined $action->namespace) {
187 $error .= qq/Action has no namespace: cannot $opname() to a plain /
188 .qq/method or component, must be a :Action or some sort./
189 }
190 elsif (!$action->class->can('_DISPATCH')) {
191 $error .= qq/Action cannot _DISPATCH. /
192 .qq/Did you try to $opname() a non-controller action?/;
193 }
194 else {
195 $error = q();
196 }
197
198 if($error) {
2f381252 199 $c->error($error);
200 $c->log->debug($error) if $c->debug;
201 return 0;
202 }
203
52f71256 204 $action = $self->expand_action($action);
205
2f381252 206 local $c->request->{arguments} = $args;
ae0e35ee 207 local $c->{namespace} = $action->{'namespace'};
208 local $c->{action} = $action;
209
2f381252 210 $self->dispatch($c);
ae0e35ee 211}
212
213=head2 $self->go( $c, $command [, \@arguments ] )
214
215Documented in L<Catalyst>
216
217=cut
2f381252 218
ae0e35ee 219sub go {
220 my $self = shift;
221 $self->_do_visit('go', @_);
2f381252 222 die $Catalyst::GO;
223}
224
225=head2 $self->forward( $c, $command [, \@arguments ] )
226
227Documented in L<Catalyst>
228
229=cut
230
231sub forward {
232 my $self = shift;
3ea37672 233 $self->_do_forward(forward => @_);
234}
235
236sub _do_forward {
237 my $self = shift;
238 my $opname = shift;
2f381252 239 my ( $c, $command ) = @_;
240 my ( $action, $args ) = $self->_command2action(@_);
99fe1710 241
3ea37672 242 if (!$action) {
243 my $error .= qq/Couldn't $opname to command "$command": /
244 .qq/Invalid action or component./;
e540158b 245 $c->error($error);
246 $c->log->debug($error) if $c->debug;
247 return 0;
248 }
bd7d2e94 249
059c085b 250 no warnings 'recursion';
251
12f0342e 252 local $c->request->{arguments} = $args;
b8f669f3 253 $action->dispatch( $c );
3ea37672 254
1abd6db7 255 return $c->state;
256}
257
3ea37672 258=head2 $self->detach( $c, $command [, \@arguments ] )
259
260Documented in L<Catalyst>
261
262=cut
263
264sub detach {
265 my ( $self, $c, $command, @args ) = @_;
266 $self->_do_forward(detach => $c, $command, @args ) if $command;
267 die $Catalyst::DETACH;
268}
269
adb53907 270sub _action_rel2abs {
e540158b 271 my ( $self, $c, $path ) = @_;
272
273 unless ( $path =~ m#^/# ) {
274 my $namespace = $c->stack->[-1]->namespace;
275 $path = "$namespace/$path";
276 }
277
278 $path =~ s#^/##;
279 return $path;
adb53907 280}
281
282sub _invoke_as_path {
e540158b 283 my ( $self, $c, $rel_path, $args ) = @_;
284
e540158b 285 my $path = $self->_action_rel2abs( $c, $rel_path );
286
287 my ( $tail, @extra_args );
288 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
289 { # allow $path to be empty
290 if ( my $action = $c->get_action( $tail, $path ) ) {
291 push @$args, @extra_args;
292 return $action;
293 }
294 else {
295 return
296 unless $path
297 ; # if a match on the global namespace failed then the whole lookup failed
298 }
299
300 unshift @extra_args, $tail;
301 }
adb53907 302}
303
304sub _find_component_class {
e540158b 305 my ( $self, $c, $component ) = @_;
adb53907 306
e540158b 307 return ref($component)
308 || ref( $c->component($component) )
309 || $c->component($component);
adb53907 310}
311
312sub _invoke_as_component {
e540158b 313 my ( $self, $c, $component, $method ) = @_;
314
315 my $class = $self->_find_component_class( $c, $component ) || return 0;
e540158b 316
317 if ( my $code = $class->can($method) ) {
c41cfce3 318 return $self->_method_action_class->new(
e540158b 319 {
320 name => $method,
321 code => $code,
322 reverse => "$class->$method",
323 class => $class,
324 namespace => Catalyst::Utils::class2prefix(
325 $class, $c->config->{case_sensitive}
326 ),
327 }
328 );
329 }
330 else {
331 my $error =
332 qq/Couldn't forward to "$class". Does not implement "$method"/;
333 $c->error($error);
334 $c->log->debug($error)
335 if $c->debug;
336 return 0;
337 }
adb53907 338}
339
b5ecfcf0 340=head2 $self->prepare_action($c)
fbcc39ad 341
4ab87e27 342Find an dispatch type that matches $c->req->path, and set args from it.
343
fbcc39ad 344=cut
345
346sub prepare_action {
347 my ( $self, $c ) = @_;
e63bdf38 348 my $req = $c->req;
349 my $path = $req->path;
350 my @path = split /\//, $req->path;
351 $req->args( \my @args );
fbcc39ad 352
61a9002d 353 unshift( @path, '' ); # Root action
78d760bb 354
b96f127f 355 DESCEND: while (@path) {
fbcc39ad 356 $path = join '/', @path;
61a9002d 357 $path =~ s#^/##;
fbcc39ad 358
61a9002d 359 $path = '' if $path eq '/'; # Root action
78d760bb 360
22f3a8dd 361 # Check out dispatch types to see if any will handle the path at
362 # this level
363
c41cfce3 364 foreach my $type ( @{ $self->_dispatch_types } ) {
2633d7dc 365 last DESCEND if $type->match( $c, $path );
66e28e3f 366 }
b96f127f 367
22f3a8dd 368 # If not, move the last part path to args
4082e678 369 my $arg = pop(@path);
370 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
371 unshift @args, $arg;
fbcc39ad 372 }
373
e63bdf38 374 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
66d7ad40 375
e63bdf38 376 $c->log->debug( 'Path is "' . $req->match . '"' )
076bfad3 377 if ( $c->debug && defined $req->match && length $req->match );
e3a13771 378
fbcc39ad 379 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
380 if ( $c->debug && @args );
381}
382
b5ecfcf0 383=head2 $self->get_action( $action, $namespace )
1abd6db7 384
4ab87e27 385returns a named action from a given namespace.
386
1abd6db7 387=cut
388
389sub get_action {
bcd1002b 390 my ( $self, $name, $namespace ) = @_;
79a3189a 391 return unless $name;
3d0d6d21 392
2f381252 393 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
99fe1710 394
c41cfce3 395 return $self->_action_hash->{"${namespace}/${name}"};
1abd6db7 396}
397
ac5c933b 398=head2 $self->get_action_by_path( $path );
068c0898 399
ac5c933b 400Returns the named action by its full path.
3d0d6d21 401
068c0898 402=cut
3d0d6d21 403
404sub get_action_by_path {
405 my ( $self, $path ) = @_;
ea0e58d9 406 $path =~ s/^\///;
28928de9 407 $path = "/$path" unless $path =~ /\//;
c41cfce3 408 $self->_action_hash->{$path};
3d0d6d21 409}
410
b5ecfcf0 411=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 412
413=cut
414
415sub get_actions {
416 my ( $self, $c, $action, $namespace ) = @_;
417 return [] unless $action;
3d0d6d21 418
28928de9 419 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 420
421 my @match = $self->get_containers($namespace);
422
684d10ed 423 return map { $_->get_action($action) } @match;
a9dc674c 424}
425
b5ecfcf0 426=head2 $self->get_containers( $namespace )
cfd04b0c 427
4ab87e27 428Return all the action containers for a given namespace, inclusive
429
cfd04b0c 430=cut
431
432sub get_containers {
433 my ( $self, $namespace ) = @_;
a13e21ab 434 $namespace ||= '';
435 $namespace = '' if $namespace eq '/';
cfd04b0c 436
a13e21ab 437 my @containers;
cfd04b0c 438
7f23827b 439 if ( length $namespace ) {
440 do {
c41cfce3 441 push @containers, $self->_container_hash->{$namespace};
7f23827b 442 } while ( $namespace =~ s#/[^/]+$## );
443 }
90ce41ba 444
c41cfce3 445 return reverse grep { defined } @containers, $self->_container_hash->{''};
90ce41ba 446
e63bdf38 447 #return (split '/', $namespace); # isnt this more clear?
a13e21ab 448 my @parts = split '/', $namespace;
cfd04b0c 449}
450
ea0e58d9 451=head2 $self->uri_for_action($action, \@captures)
452
453Takes a Catalyst::Action object and action parameters and returns a URI
454part such that if $c->req->path were this URI part, this action would be
455dispatched to with $c->req->captures set to the supplied arrayref.
456
457If the action object is not available for external dispatch or the dispatcher
458cannot determine an appropriate URI, this method will return undef.
459
460=cut
461
462sub uri_for_action {
463 my ( $self, $action, $captures) = @_;
464 $captures ||= [];
c41cfce3 465 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
ea0e58d9 466 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 467 return( $uri eq '' ? '/' : $uri )
468 if defined($uri);
ea0e58d9 469 }
470 return undef;
471}
472
8f5a2bd9 473=head2 expand_action
ae0e35ee 474
475expand an action into a full representation of the dispatch.
476mostly useful for chained, other actions will just return a
477single action.
478
479=cut
480
52f71256 481sub expand_action {
482 my ($self, $action) = @_;
483
c41cfce3 484 foreach my $dispatch_type (@{ $self->_dispatch_types }) {
52f71256 485 my $expanded = $dispatch_type->expand_action($action);
486 return $expanded if $expanded;
487 }
488
489 return $action;
490}
491
b5ecfcf0 492=head2 $self->register( $c, $action )
aad72cc9 493
4ab87e27 494Make sure all required dispatch types for this action are loaded, then
495pass the action to our dispatch types so they can register it if required.
496Also, set up the tree with the action containers.
497
aad72cc9 498=cut
499
79a3189a 500sub register {
501 my ( $self, $c, $action ) = @_;
502
c41cfce3 503 my $registered = $self->_registered_dispatch_types;
694d15f1 504
e63bdf38 505 #my $priv = 0; #seems to be unused
694d15f1 506 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 507 next if $key eq 'Private';
694d15f1 508 my $class = "Catalyst::DispatchType::$key";
509 unless ( $registered->{$class} ) {
c41cfce3 510 # FIXME - Some error checking and re-throwing needed here, as
511 # we eat exceptions loading dispatch types.
068c0898 512 eval { Class::MOP::load_class($class) };
c41cfce3 513 push( @{ $self->_dispatch_types }, $class->new ) unless $@;
694d15f1 514 $registered->{$class} = 1;
515 }
516 }
517
518 # Pass the action to our dispatch types so they can register it if reqd.
c41cfce3 519 foreach my $type ( @{ $self->_dispatch_types } ) {
9a6ecf4f 520 $type->register( $c, $action );
694d15f1 521 }
522
79a3189a 523 my $namespace = $action->namespace;
a13e21ab 524 my $name = $action->name;
c7116517 525
ad5e4650 526 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 527
528 # Set the method value
a13e21ab 529 $container->add_action($action);
c7116517 530
c41cfce3 531 $self->_action_hash->{"$namespace/$name"} = $action;
532 $self->_container_hash->{$namespace} = $container;
15e9b5dd 533}
534
ad5e4650 535sub _find_or_create_action_container {
a13e21ab 536 my ( $self, $namespace ) = @_;
537
c41cfce3 538 my $tree ||= $self->_tree;
99fe1710 539
a13e21ab 540 return $tree->getNodeValue unless $namespace;
78d760bb 541
a13e21ab 542 my @namespace = split '/', $namespace;
543 return $self->_find_or_create_namespace_node( $tree, @namespace )
544 ->getNodeValue;
8505565b 545}
90ce41ba 546
8505565b 547sub _find_or_create_namespace_node {
a13e21ab 548 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 549
a13e21ab 550 return $parent unless $part;
8505565b 551
a13e21ab 552 my $child =
553 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 554
a13e21ab 555 unless ($child) {
556 my $container = Catalyst::ActionContainer->new($part);
557 $parent->addChild( $child = Tree::Simple->new($container) );
558 }
99fe1710 559
a13e21ab 560 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 561}
562
4ab87e27 563=head2 $self->setup_actions( $class, $context )
564
8f5a2bd9 565Loads all of the preload dispatch types, registers their actions and then
566loads all of the postload dispatch types, and does dispatcher initialization.
1abd6db7 567
568=cut
569
570sub setup_actions {
11bd4e3e 571 my ( $self, $c ) = @_;
99fe1710 572
9e81ba44 573 my @classes =
ad5e4650 574 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
c41cfce3 575 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 576
49070d25 577 foreach my $comp ( values %{ $c->components } ) {
578 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 579 }
e494bd6b 580
ad5e4650 581 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 582
11bd4e3e 583 return unless $c->debug;
99fe1710 584
39fc2ce1 585 my $column_width = Catalyst::Utils::term_width() - 20 - 36 - 12;
684d10ed 586 my $privates = Text::SimpleTable->new(
39fc2ce1 587 [ 20, 'Private' ], [ 36, 'Class' ], [ $column_width, 'Method' ]
684d10ed 588 );
99fe1710 589
87b85407 590 my $has_private = 0;
1abd6db7 591 my $walker = sub {
592 my ( $walker, $parent, $prefix ) = @_;
593 $prefix .= $parent->getNodeValue || '';
594 $prefix .= '/' unless $prefix =~ /\/$/;
b7aebc12 595 my $node = $parent->getNodeValue->actions;
99fe1710 596
78d760bb 597 for my $action ( keys %{$node} ) {
b7aebc12 598 my $action_obj = $node->{$action};
b0bb11ec 599 next
600 if ( ( $action =~ /^_.*/ )
601 && ( !$c->config->{show_internal_actions} ) );
684d10ed 602 $privates->row( "$prefix$action", $action_obj->class, $action );
87b85407 603 $has_private = 1;
1abd6db7 604 }
99fe1710 605
1abd6db7 606 $walker->( $walker, $_, $prefix ) for $parent->getAllChildren;
607 };
99fe1710 608
c41cfce3 609 $walker->( $walker, $self->_tree, '' );
1cf0345b 610 $c->log->debug( "Loaded Private actions:\n" . $privates->draw . "\n" )
611 if $has_private;
99fe1710 612
a9cbd748 613 # List all public actions
c41cfce3 614 $_->list($c) for @{ $self->_dispatch_types };
1abd6db7 615}
616
ad5e4650 617sub _load_dispatch_types {
9e81ba44 618 my ( $self, @types ) = @_;
619
620 my @loaded;
621
622 # Preload action types
623 for my $type (@types) {
624 my $class =
625 ( $type =~ /^\+(.*)$/ ) ? $1 : "Catalyst::DispatchType::${type}";
0fc2d522 626
068c0898 627 eval { Class::MOP::load_class($class) };
9e81ba44 628 Catalyst::Exception->throw( message => qq/Couldn't load "$class"/ )
629 if $@;
c41cfce3 630 push @{ $self->_dispatch_types }, $class->new;
9e81ba44 631
632 push @loaded, $class;
633 }
634
a13e21ab 635 return @loaded;
9e81ba44 636}
637
c41cfce3 638use Moose;
639
640# 5.70 backwards compatibility hacks.
641
642# Various plugins (e.g. Plugin::Server and Plugin::Authorization::ACL)
643# need the methods here which *should* be private..
644
645# However we can't really take them away until there is a sane API for
646# building actions and configuring / introspecting the dispatcher.
647# In 5.90, we should build that infrastructure, port the plugins which
648# use it, and then take the crap below away.
649# See also t/lib/TestApp/Plugin/AddDispatchTypes.pm
650
651# Alias _method_name to method_name, add a before modifier to warn..
652foreach my $public_method_name (qw/
653 tree
654 dispatch_types
655 registered_dispatch_types
656 method_action_class
657 action_hash
658 container_hash
659 /) {
660 my $private_method_name = '_' . $public_method_name;
661 my $meta = __PACKAGE__->meta; # Calling meta method here fine as we happen at compile time.
662 $meta->add_method($public_method_name, $meta->get_method($private_method_name));
663 {
664 my %package_hash; # Only warn once per method, per package. These are infrequent enough that
665 # I haven't provided a way to disable them, patches welcome.
666 $meta->add_before_method_modifier($public_method_name, sub {
7e95ba12 667 my $class = blessed(shift);
c41cfce3 668 $package_hash{$class}++ || do {
669 warn("Class $class is calling the deprecated method Catalyst::Dispatcher::$public_method_name,\n"
670 . "this will be removed in Catalyst 5.9X");
671 };
672 });
673 }
674}
675# End 5.70 backwards compatibility hacks.
676
6680c772 677no Moose;
e5ecd5bc 678__PACKAGE__->meta->make_immutable;
679
059c085b 680=head2 meta
681
682Provided by Moose
683
2f381252 684=head1 AUTHORS
1abd6db7 685
2f381252 686Catalyst Contributors, see Catalyst.pm
1abd6db7 687
688=head1 COPYRIGHT
689
690This program is free software, you can redistribute it and/or modify it under
691the same terms as Perl itself.
692
693=cut
694
6951;