Add IDEAS as a place to dump random ideas on how things could be improved.
[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
12f0342e 254 local $c->request->{arguments} = $args;
b8f669f3 255 $action->dispatch( $c );
3ea37672 256
1abd6db7 257 return $c->state;
258}
259
3ea37672 260=head2 $self->detach( $c, $command [, \@arguments ] )
261
262Documented in L<Catalyst>
263
264=cut
265
266sub detach {
267 my ( $self, $c, $command, @args ) = @_;
268 $self->_do_forward(detach => $c, $command, @args ) if $command;
269 die $Catalyst::DETACH;
270}
271
adb53907 272sub _action_rel2abs {
e540158b 273 my ( $self, $c, $path ) = @_;
274
275 unless ( $path =~ m#^/# ) {
276 my $namespace = $c->stack->[-1]->namespace;
277 $path = "$namespace/$path";
278 }
279
280 $path =~ s#^/##;
281 return $path;
adb53907 282}
283
284sub _invoke_as_path {
e540158b 285 my ( $self, $c, $rel_path, $args ) = @_;
286
e540158b 287 my $path = $self->_action_rel2abs( $c, $rel_path );
288
289 my ( $tail, @extra_args );
290 while ( ( $path, $tail ) = ( $path =~ m#^(?:(.*)/)?(\w+)?$# ) )
291 { # allow $path to be empty
292 if ( my $action = $c->get_action( $tail, $path ) ) {
293 push @$args, @extra_args;
294 return $action;
295 }
296 else {
297 return
298 unless $path
299 ; # if a match on the global namespace failed then the whole lookup failed
300 }
301
302 unshift @extra_args, $tail;
303 }
adb53907 304}
305
306sub _find_component_class {
e540158b 307 my ( $self, $c, $component ) = @_;
adb53907 308
e540158b 309 return ref($component)
310 || ref( $c->component($component) )
311 || $c->component($component);
adb53907 312}
313
314sub _invoke_as_component {
e540158b 315 my ( $self, $c, $component, $method ) = @_;
316
317 my $class = $self->_find_component_class( $c, $component ) || return 0;
e540158b 318
319 if ( my $code = $class->can($method) ) {
c41cfce3 320 return $self->_method_action_class->new(
e540158b 321 {
322 name => $method,
323 code => $code,
324 reverse => "$class->$method",
325 class => $class,
326 namespace => Catalyst::Utils::class2prefix(
327 $class, $c->config->{case_sensitive}
328 ),
329 }
330 );
331 }
332 else {
333 my $error =
334 qq/Couldn't forward to "$class". Does not implement "$method"/;
335 $c->error($error);
336 $c->log->debug($error)
337 if $c->debug;
338 return 0;
339 }
adb53907 340}
341
b5ecfcf0 342=head2 $self->prepare_action($c)
fbcc39ad 343
4ab87e27 344Find an dispatch type that matches $c->req->path, and set args from it.
345
fbcc39ad 346=cut
347
348sub prepare_action {
349 my ( $self, $c ) = @_;
e63bdf38 350 my $req = $c->req;
351 my $path = $req->path;
352 my @path = split /\//, $req->path;
353 $req->args( \my @args );
fbcc39ad 354
61a9002d 355 unshift( @path, '' ); # Root action
78d760bb 356
b96f127f 357 DESCEND: while (@path) {
fbcc39ad 358 $path = join '/', @path;
61a9002d 359 $path =~ s#^/##;
fbcc39ad 360
61a9002d 361 $path = '' if $path eq '/'; # Root action
78d760bb 362
22f3a8dd 363 # Check out dispatch types to see if any will handle the path at
364 # this level
365
c41cfce3 366 foreach my $type ( @{ $self->_dispatch_types } ) {
2633d7dc 367 last DESCEND if $type->match( $c, $path );
66e28e3f 368 }
b96f127f 369
22f3a8dd 370 # If not, move the last part path to args
4082e678 371 my $arg = pop(@path);
372 $arg =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
373 unshift @args, $arg;
fbcc39ad 374 }
375
e63bdf38 376 s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg for grep { defined } @{$req->captures||[]};
66d7ad40 377
e63bdf38 378 $c->log->debug( 'Path is "' . $req->match . '"' )
076bfad3 379 if ( $c->debug && defined $req->match && length $req->match );
e3a13771 380
fbcc39ad 381 $c->log->debug( 'Arguments are "' . join( '/', @args ) . '"' )
382 if ( $c->debug && @args );
383}
384
b5ecfcf0 385=head2 $self->get_action( $action, $namespace )
1abd6db7 386
4ab87e27 387returns a named action from a given namespace.
388
1abd6db7 389=cut
390
391sub get_action {
bcd1002b 392 my ( $self, $name, $namespace ) = @_;
79a3189a 393 return unless $name;
3d0d6d21 394
2f381252 395 $namespace = join( "/", grep { length } split '/', ( defined $namespace ? $namespace : "" ) );
99fe1710 396
c41cfce3 397 return $self->_action_hash->{"${namespace}/${name}"};
1abd6db7 398}
399
ac5c933b 400=head2 $self->get_action_by_path( $path );
068c0898 401
ac5c933b 402Returns the named action by its full path.
3d0d6d21 403
068c0898 404=cut
3d0d6d21 405
406sub get_action_by_path {
407 my ( $self, $path ) = @_;
ea0e58d9 408 $path =~ s/^\///;
28928de9 409 $path = "/$path" unless $path =~ /\//;
c41cfce3 410 $self->_action_hash->{$path};
3d0d6d21 411}
412
b5ecfcf0 413=head2 $self->get_actions( $c, $action, $namespace )
a9dc674c 414
415=cut
416
417sub get_actions {
418 my ( $self, $c, $action, $namespace ) = @_;
419 return [] unless $action;
3d0d6d21 420
28928de9 421 $namespace = join( "/", grep { length } split '/', $namespace || "" );
a9dc674c 422
423 my @match = $self->get_containers($namespace);
424
684d10ed 425 return map { $_->get_action($action) } @match;
a9dc674c 426}
427
b5ecfcf0 428=head2 $self->get_containers( $namespace )
cfd04b0c 429
4ab87e27 430Return all the action containers for a given namespace, inclusive
431
cfd04b0c 432=cut
433
434sub get_containers {
435 my ( $self, $namespace ) = @_;
a13e21ab 436 $namespace ||= '';
437 $namespace = '' if $namespace eq '/';
cfd04b0c 438
a13e21ab 439 my @containers;
cfd04b0c 440
7f23827b 441 if ( length $namespace ) {
442 do {
c41cfce3 443 push @containers, $self->_container_hash->{$namespace};
7f23827b 444 } while ( $namespace =~ s#/[^/]+$## );
445 }
90ce41ba 446
c41cfce3 447 return reverse grep { defined } @containers, $self->_container_hash->{''};
90ce41ba 448
e63bdf38 449 #return (split '/', $namespace); # isnt this more clear?
a13e21ab 450 my @parts = split '/', $namespace;
cfd04b0c 451}
452
ea0e58d9 453=head2 $self->uri_for_action($action, \@captures)
454
455Takes a Catalyst::Action object and action parameters and returns a URI
456part such that if $c->req->path were this URI part, this action would be
457dispatched to with $c->req->captures set to the supplied arrayref.
458
459If the action object is not available for external dispatch or the dispatcher
460cannot determine an appropriate URI, this method will return undef.
461
462=cut
463
464sub uri_for_action {
465 my ( $self, $action, $captures) = @_;
466 $captures ||= [];
c41cfce3 467 foreach my $dispatch_type ( @{ $self->_dispatch_types } ) {
ea0e58d9 468 my $uri = $dispatch_type->uri_for_action( $action, $captures );
81e75875 469 return( $uri eq '' ? '/' : $uri )
470 if defined($uri);
ea0e58d9 471 }
472 return undef;
473}
474
ae0e35ee 475=head2 expand_action
476
477expand an action into a full representation of the dispatch.
478mostly useful for chained, other actions will just return a
479single action.
480
481=cut
482
52f71256 483sub expand_action {
484 my ($self, $action) = @_;
485
c41cfce3 486 foreach my $dispatch_type (@{ $self->_dispatch_types }) {
52f71256 487 my $expanded = $dispatch_type->expand_action($action);
488 return $expanded if $expanded;
489 }
490
491 return $action;
492}
493
b5ecfcf0 494=head2 $self->register( $c, $action )
aad72cc9 495
4ab87e27 496Make sure all required dispatch types for this action are loaded, then
497pass the action to our dispatch types so they can register it if required.
498Also, set up the tree with the action containers.
499
aad72cc9 500=cut
501
79a3189a 502sub register {
503 my ( $self, $c, $action ) = @_;
504
c41cfce3 505 my $registered = $self->_registered_dispatch_types;
694d15f1 506
e63bdf38 507 #my $priv = 0; #seems to be unused
694d15f1 508 foreach my $key ( keys %{ $action->attributes } ) {
9a6ecf4f 509 next if $key eq 'Private';
694d15f1 510 my $class = "Catalyst::DispatchType::$key";
511 unless ( $registered->{$class} ) {
c41cfce3 512 # FIXME - Some error checking and re-throwing needed here, as
513 # we eat exceptions loading dispatch types.
068c0898 514 eval { Class::MOP::load_class($class) };
c41cfce3 515 push( @{ $self->_dispatch_types }, $class->new ) unless $@;
694d15f1 516 $registered->{$class} = 1;
517 }
518 }
519
520 # Pass the action to our dispatch types so they can register it if reqd.
c41cfce3 521 foreach my $type ( @{ $self->_dispatch_types } ) {
9a6ecf4f 522 $type->register( $c, $action );
694d15f1 523 }
524
79a3189a 525 my $namespace = $action->namespace;
a13e21ab 526 my $name = $action->name;
c7116517 527
ad5e4650 528 my $container = $self->_find_or_create_action_container($namespace);
15e9b5dd 529
530 # Set the method value
a13e21ab 531 $container->add_action($action);
c7116517 532
c41cfce3 533 $self->_action_hash->{"$namespace/$name"} = $action;
534 $self->_container_hash->{$namespace} = $container;
15e9b5dd 535}
536
ad5e4650 537sub _find_or_create_action_container {
a13e21ab 538 my ( $self, $namespace ) = @_;
539
c41cfce3 540 my $tree ||= $self->_tree;
99fe1710 541
a13e21ab 542 return $tree->getNodeValue unless $namespace;
78d760bb 543
a13e21ab 544 my @namespace = split '/', $namespace;
545 return $self->_find_or_create_namespace_node( $tree, @namespace )
546 ->getNodeValue;
8505565b 547}
90ce41ba 548
8505565b 549sub _find_or_create_namespace_node {
a13e21ab 550 my ( $self, $parent, $part, @namespace ) = @_;
78d760bb 551
a13e21ab 552 return $parent unless $part;
8505565b 553
a13e21ab 554 my $child =
555 ( grep { $_->getNodeValue->part eq $part } $parent->getAllChildren )[0];
8505565b 556
a13e21ab 557 unless ($child) {
558 my $container = Catalyst::ActionContainer->new($part);
559 $parent->addChild( $child = Tree::Simple->new($container) );
560 }
99fe1710 561
a13e21ab 562 $self->_find_or_create_namespace_node( $child, @namespace );
1abd6db7 563}
564
4ab87e27 565=head2 $self->setup_actions( $class, $context )
566
1abd6db7 567
568=cut
569
570sub setup_actions {
11bd4e3e 571 my ( $self, $c ) = @_;
99fe1710 572
12e28165 573
9e81ba44 574 my @classes =
ad5e4650 575 $self->_load_dispatch_types( @{ $self->preload_dispatch_types } );
c41cfce3 576 @{ $self->_registered_dispatch_types }{@classes} = (1) x @classes;
b96f127f 577
49070d25 578 foreach my $comp ( values %{ $c->components } ) {
579 $comp->register_actions($c) if $comp->can('register_actions');
1abd6db7 580 }
e494bd6b 581
ad5e4650 582 $self->_load_dispatch_types( @{ $self->postload_dispatch_types } );
6d030e6f 583
11bd4e3e 584 return unless $c->debug;
99fe1710 585
684d10ed 586 my $privates = Text::SimpleTable->new(
dbf03873 587 [ 20, 'Private' ],
34d28dfd 588 [ 36, 'Class' ],
dbf03873 589 [ 12, '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;