Cataly...Chained(3pm)
π Catalyst::DispatchType::Chained π β the Cataly...Chained(3pm) era
ok so bestie, welcome to Catalyst::DispatchType::Chained, which the header of this thing calls Cataly...Chained(3pm) because apparently even manpages get nickname'd. It's part of the User Contributed Perl Documentation and it's about making your URLs chain together like the cutest little relay race you've ever seen πβοΈβ¨
β¨ NAME β¨
Catalyst::DispatchType::Chained β Path Part DispatchType. that's it, that's the name, she's simple and she knows it π
πΈ SYNOPSIS πΈ
Path part matching, babes β it lets several actions take turns handling one single request, one after another, in order. no cap.
# root action - captures one argument after it
sub foo_setup : Chained('/') PathPart('foo') CaptureArgs(1) {
my ( $self, $c, $foo_arg ) = @_;
...
}
# child action endpoint - takes one argument
sub bar : Chained('foo_setup') Args(1) {
my ( $self, $c, $bar_arg ) = @_;
...
}
see how foo_setup grabs the path part and then hands the vibe off to bar? that's the whole bit π
π¦ DESCRIPTION π¦
This is the dispatch type that manages the default behaviour. if you wanna go deeper on dispatch types in general (and honestly, main character energy, you should), check these out:
Catalyst::Manual::Introβ for how they affect application authors (that's you π)Catalyst::DispatchTypeβ for the implementation info, iykyk
π METHODS π
$self->list($c)
Debug output for Path Part dispatch points. it's giving "let me show you the receipts" π§Ύ
$self->match( $c, $path )
Calls recurse_match to see if a chain matches the $path. like a vibe check but for paths.
$self->recurse_match( $c, $parent, \@path_parts )
Recursive search for a matching chain. it goes in, it goes deep, it finds the chain π
$self->register( $c, $action )
Calls register_path for every Path attribute for the given $action.
$self->uri_for_action($action, $captures)
Get the URI part for the action, using $captures to fill in the capturing parts. lowkey so useful.
$c->expand_action($action)
Return a list of actions that represents a chained action. see Catalyst::Dispatcher for more info. honestly, you probably want to use the expand_action it provides rather than this one directly β trust π
π USAGE π
π« Introduction π«
The Chained attribute lets you chain public path parts together by their private names. a chain part's path can be specified with PathPart, and you can declare that it expects an arbitrary number of arguments. the endpoint of the chain is the one that specifies how many arguments it gets, via the Args attribute. :Args(0) would be none at all, :Args without an integer would be unlimited. the path parts that aren't endpoints use CaptureArgs to say how many parameters they expect. here's an example setup, and yes it slays:
package MyApp::Controller::Greeting;
use base qw/ Catalyst::Controller /;
# this is the beginning of our chain
sub hello : PathPart('hello') Chained('/') CaptureArgs(1) {
my ( $self, $c, $integer ) = @_;
$c->stash->{ message } = "Hello ";
$c->stash->{ arg_sum } = $integer;
}
# this is our endpoint, because it has no :CaptureArgs
sub world : PathPart('world') Chained('hello') Args(1) {
my ( $self, $c, $integer ) = @_;
$c->stash->{ message } .= "World!";
$c->stash->{ arg_sum } += $integer;
$c->response->body( join "<br/>\n" =>
$c->stash->{ message }, $c->stash->{ arg_sum } );
}
The debug output gives chained actions their own separate table, showing the whole chain as it would match plus the actions it contains. here's the startup output for the actions above:
...
[debug] Loaded Path Part actions:
.-----------------------+------------------------------.
| Path Spec | Private |
+-----------------------+------------------------------+
| /hello/*/world/* | /greeting/hello (1) |
| | => /greeting/world |
'-----------------------+------------------------------'
...
so as you can see, Catalyst only deals with chains as whole paths, and it builds one for each endpoint β and endpoints are the actions with :Chained but without :CaptureArgs. periodt.
Now let's say this app gets a request at the path /hello/23/world/12. what happens?? first, Catalyst dispatches to the hello action and passes the value 23 to it as an argument, after the context. it does that because we already used :CaptureArgs(1) to declare that it has one path part after itself as its argument. we told Catalyst this is the beginning of the chain by specifying :Chained('/'). also note: instead of saying :PathPart('hello') we could've just said :PathPart, because it defaults to the name of the action. cute, right?
After hello has run, Catalyst goes on to dispatch to the world action. this is the last action to be called: Catalyst knows it's an endpoint because we didn't specify a :CaptureArgs attribute. nevertheless we specify that this action expects an argument β but at this point we're using :Args(1) to do that. we could also have said :Args or left it out altogether, which would mean this action would get all the arguments that are there. this action's :Chained attribute says hello, which tells Catalyst that the hello action in the current controller is its parent. π
and with that we've built a chain of two public path parts. hello captures one part of the path as its argument, and also specifies the path root as its parent. so this part is /hello/$arg. the next part is the endpoint world, expecting one argument. it all sums up to the path part world/$arg. that leads to a complete chain of /hello/$arg/world/$arg, which gets matched against the requested paths.
This example application, if you run it and call it with e.g. /hello/23/world/12, would set the stash value message to Hello and the value arg_sum to 23. the world action would then append World! to message and add 12 to the stash's arg_sum value. for the sake of simplicity no view is shown β instead we just put the values of the stash into our body. so the output would look like:
Hello World!
35
and our test server would have given us this debugging output for the request:
...
[debug] "GET" request for "hello/23/world/12" from "127.0.0.1"
[debug] Path is "/greeting/world"
[debug] Arguments are "12"
[info] Request took 0.164113s (6.093/s)
.------------------------------------------+-----------.
| Action | Time |
+------------------------------------------+-----------+
| /greeting/hello | 0.000029s |
| /greeting/world | 0.000024s |
'------------------------------------------+-----------'
...
so likeβ¦ what would be common uses of this dispatch technique? π€ it gives you the possibility to split up logic that contains steps where each one depends on the one before it. an example would be a wiki path like /wiki/FooBarPage/rev/23/view. that chain can be easily built with these actions:
sub wiki : PathPart('wiki') Chained('/') CaptureArgs(1) {
my ( $self, $c, $page_name ) = @_;
# load the page named $page_name and put the object
# into the stash
}
sub rev : PathPart('rev') Chained('wiki') CaptureArgs(1) {
my ( $self, $c, $revision_id ) = @_;
# use the page object in the stash to get at its
# revision with number $revision_id
}
sub view : PathPart Chained('rev') Args(0) {
my ( $self, $c ) = @_;
# display the revision in our stash. Another option
# would be to forward a compatible object to the action
# that displays the default wiki pages, unless we want
# a different interface here, for example restore
# functionality.
}
it would now be possible to add other endpoints, for example restore, to restore this specific revision as the current state. iconic behaviour honestly.
You do not have to put all the chained actions in one controller. the specification of the parent through :Chained also takes an absolute action path as its argument β just specify it with a leading /.
If you want, for example, to have actions for the public paths /foo/12/edit and /foo/12, just specify two actions with :PathPart('foo') and :Chained('/'). the handler for the former path needs a :CaptureArgs(1) attribute and an endpoint with :PathPart('edit') and :Chained('foo'). for the latter path, give the action just a :Args(1) to mark it as an endpoint. this sums up to this debugging output:
...
[debug] Loaded Path Part actions:
.-----------------------+------------------------------.
| Path Spec | Private |
+-----------------------+------------------------------+
| /foo/* | /controller/foo_view |
| /foo/*/edit | /controller/foo_load (1) |
| | => /controller/edit |
'-----------------------+------------------------------'
...
here's a more detailed specification of the attributes belonging to :Chained:
π Attributes π
π PathPart π
Sets the name of this part of the chain. if it's specified without arguments, it takes the name of the action as default. so basically sub foo :PathPart and sub foo :PathPart('foo') are identical. this can also contain slashes to bind to a deeper level. an action with sub bar :PathPart('foo/bar') :Chained('/') would bind to /foo/bar/.... and if you don't specify :PathPart at all, it has the same effect as using :PathPart β it would default to the action name. π
π PathPrefix π
Sets PathPart to the path_prefix of the current controller.
π Chained π
Has to be specified for every child in the chain. possible values are absolute and relative private action paths, or a single slash / to tell Catalyst that this is the root of a chain. the attribute :Chained without arguments also defaults to the / behavior. relative action paths may use ../ to refer to actions in parent controllers.
Because you can specify an absolute path to the parent action, it doesn't matter to Catalyst where that parent is located. so if your design requests it, you can redispatch a chain through any controller or namespace you want. π
Another interesting possibility gives :Chained('.'), which chains itself to an action with the path of the current controller's namespace. for example:
# in MyApp::Controller::Foo
sub bar : Chained CaptureArgs(1) { ... }
# in MyApp::Controller::Foo::Bar
sub baz : Chained('.') Args(1) { ... }
This builds up a chain like /bar/*/baz/*. the specification of . as the argument to Chained here chains the baz action to an action with the path of the current controller namespace, namely /foo/bar. that action chains directly to /, so the /bar/*/baz/* chain comes out as the end product. β¨
π ChainedParent π
Chains an action to another action with the same name in the parent controller. for example:
# in MyApp::Controller::Foo
sub bar : Chained CaptureArgs(1) { ... }
# in MyApp::Controller::Foo::Bar
sub bar : ChainedParent Args(1) { ... }
This builds a chain like /bar/*/bar/*. Bestie that asterisk is doing so much heavy lifting rn π
CaptureArgs
Okay so this one is mandatory for every single part of the chain that is not an endpoint, no skipsies π«Ά
- It's how Catalyst knows how many of the following parts of the path (the ones split by
/) this action wants to capture as its own arguments. - If it doesn't expect any? Just say
:CaptureArgs(0)and move on, easy. - Those captures get handed to the action's
@_right after the context β but you can also find them chilling as array references in$c->request->captures->[$level]. - That
$levelis the level of the action in the chain that captured those path parts.
And this part matters, so listen up π β an action that's part of a chain (meaning it has a :Chained attribute) but has no :CaptureArgs attribute gets treated by Catalyst as a chain end. Yes, the absence does the thing. Not me finding that out the hard way π
Allowed values for CaptureArgs:
- a single integer β
CaptureArgs(2)meaning two allowed - or you can declare a Moose, MooseX::Types or Type::Tiny named constraint β like
CaptureArgs(Int,Str)would require two args, first one being a Integer and the second a string
You may declare your own custom type constraints and import them into the controller namespace:
package MyApp::Controller::Root;
use Moose;
use MooseX::MethodAttributes;
use MyApp::Types qw/Int/;
extends 'Catalyst::Controller';
sub chain_base :Chained(/) CaptureArgs(1) { }
sub any_priority_chain :Chained(chain_base) PathPart('') Args(1) { }
sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) { }
Heads up: if you use a reference type constraint in CaptureArgs, it must be a type like Tuple in Types::Standard that allows us to determine the number of args to match. Otherwise this will raise an error during startup. Like, full stop, it will not vibe. π
See Catalyst::RouteMatching for more. β¨
Args
So by default, endpoints receive the rest of the arguments in the path. But you can tell Catalyst through :Args explicitly how many arguments your endpoint expects β literally just like you can with :CaptureArgs. π
And here's the tea: this also affects whether this chain is invoked on a request. A chain with an endpoint specifying one argument will only match if exactly one argument exists in the path. No extras, no missing, exact. Periodt.
- You can specify an exact number of arguments like
:Args(3), and yes, including0. - If you just say
:Argswith no arguments at all, it is the same as leaving it out altogether: the chain is matched regardless of the number of path parts after the endpoint.
Just as with :CaptureArgs, the arguments get passed to the action in @_ after the context object. They can also be reached through $c->request->arguments. Two ways, same energy π¦
You should see 'Args' in Catalyst::Controller for more details on using type constraints in your Args declarations.
Auto actions, dispatching and forwarding
Okay important lil nuance here β the list of auto actions that get called depends on the private path of the endpoint of the chain, not on the chained actions way. The auto actions will be run before the chain dispatching begins. In every other aspect though, auto actions behave as documented. So no surprises beyond that, iykyk πΈ
The forwarding to other actions does just what you would expect β i.e. only the target action is run. The actions that that action is chained to are not run. Sneaky, right?
And if you detach out of a chain, the rest of the chain will not get called after the detach. That's it, that's the whole chain, bye π
match_captures
A method which can optionally be implemented by actions to stop chain matching.
See Catalyst::Action for further details. π₯
AUTHORS
Catalyst Contributors, see Catalyst.pm π
COPYRIGHT
This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself.
And that's a wrap, bestie π this whole thing is from perl v5.40.0, dated 2024-11-15, part of the User Contributed Perl Documentation. Slay, go build your chains π
β¨
π¦ where this page comes from
- debian trixie: libcatalyst-perl 5.90132-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libcatalyst-perl 5.90132-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libcatalyst-perl 5.90132-1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms