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