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