X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Runtime.git;a=blobdiff_plain;f=lib%2FCatalyst%2FDispatchType%2FChained.pm;h=2fb0a0b8086f4805b6f85ab1c6a7f174d4342c57;hp=1d7783b499cb33c66b72cd8fb414bb854e60dd91;hb=05a90578a1a58925942d520bf21333c59c6eac01;hpb=8a6a6581d48a3c1c4d5f631cdb7cbda336c0e5e2 diff --git a/lib/Catalyst/DispatchType/Chained.pm b/lib/Catalyst/DispatchType/Chained.pm index 1d7783b..2fb0a0b 100644 --- a/lib/Catalyst/DispatchType/Chained.pm +++ b/lib/Catalyst/DispatchType/Chained.pm @@ -14,10 +14,22 @@ Catalyst::DispatchType::Chained - Path Part DispatchType =head1 SYNOPSIS -See L. + # 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 ) = @_; + ... + } =head1 DESCRIPTION +See L. + =head1 METHODS =head2 $self->list($c) @@ -78,7 +90,7 @@ sub list { =head2 $self->match( $c, $path ) -Matt is an idiot and hasn't documented this yet. +Calls C to see if a chain matches the C<$path>. =cut @@ -106,7 +118,7 @@ sub match { =head2 $self->recurse_match( $c, $parent, \@path_parts ) -Matt is an idiot and hasn't documented this yet. +Recursive search for a matching chain. =cut @@ -157,7 +169,7 @@ sub recurse_match { =head2 $self->register( $c, $action ) -Matt is an idiot and hasn't documented this yet. +Calls register_path for every Path attribute for the given $action. =cut @@ -223,7 +235,8 @@ sub register { =head2 $self->uri_for_action($action, $captures) -Matt is an idiot and hasn't documented this yet. +Get the URI part for the action, using C<$captures> to fill +the capturing parts. =cut @@ -258,6 +271,255 @@ sub uri_for_action { } +=head1 USAGE + +=head2 Introduction + +The C attribute allows you to chain public path parts together +by their private names. A chain part's path can be specified with C +and can be declared to expect an arbitrary number of arguments. The endpoint +of the chain specifies how many arguments it gets through the C +attribute. C<:Args(0)> would be none at all, C<:Args> without an integer +would be unlimited. The path parts that aren't endpoints are using +C to specify how many parameters they expect to receive. As an +example setup: + + 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 "
\n" => + $c->stash->{ message }, $c->stash->{ arg_sum } ); + } + +The debug output provides a separate table for chained actions, showing +the whole chain as it would match and the actions it contains. Here's +an example of the startup output with our actions above: + + ... + [debug] Loaded Path Part actions: + .-----------------------+------------------------------. + | Path Spec | Private | + +-----------------------+------------------------------+ + | /hello/*/world/* | /greeting/hello (1) | + | | => /greeting/world | + '-----------------------+------------------------------' + ... + +As you can see, Catalyst only deals with chains as whole path and +builds one for each endpoint, which are the actions with C<:Chained> +but without C<:CaptureArgs>. + +Let's assume this application gets a request at the path +C, what happens then? First, Catalyst will dispatch +to the C action and pass the value C<23> as argument to it after +the context. It does so because we have previously used C<:CaptureArgs(1)> +to declare that it has one path part after itself as it's argument. We +told Catalyst that this is the beginning of the chain by specifying +C<:Chained('/')>. Also note that instead of saying C<:PathPart('hello')> +we could also just have said C<:PathPart>, as it defaults to the name of +the action. + +After C has run, Catalyst goes on to dispatch to the C +action. This is the last action to be called, as Catalyst knows this +is an endpoint because we specified no C<:CaptureArgs> attribute. Nevertheless +we specify that this action expects an argument, but at this point we're +using C<:Args(1)> to do that. We could also have said C<:Args> or leave +it out alltogether, which would mean this action gets all arguments that +are there. This action's C<:Chained> attribute says C and tells +Catalyst that the C action in the current controller is it's +parent. + +With this we have built a chain consisting of two public path parts. +C captures one part of the path as it's argument, and also specifies +the path root as it's parent. So this part is C. The next part +is the endpoint C, expecting one argument. It sums up to the path +part C. This leads to a complete chain of +C which is matched against the requested paths. + +This example application would, if run and called by e.g. +C, set the stash value C to C and +the value C to C<23>. The C action would then append +C to C and add C<12> to the stash's C 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've 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 | + '------------------------------------------+-----------' + ... + +What would be common usecases of this dispatching technique? It gives the +possibility to split up logic that contains steps that each depend on each +other. An example would be, for example, a wiki path like +C. This 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 it's + # revision with number $revision_id + } + + sub view : PathPart Chained('rev') Args(0) { + my ( $self, $c ) = @_; + # display the revision in our stash. An other 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 C to +restore this specific revision as current state. + +Also, you of course don't have to put all the chained actions in one +controller. The specification of the parent through C<:Chained> also takes +an absolute action path as it's argument. Just specify it with a leading +C. + +If you want, for example, to have actions for the public paths +C and C, just specify two actions with +C<:PathPart('foo')> and C<:Chained('/')>. The handler for the former +path needs a C<:CaptureArgs(1)> attribute and a endpoint with +C<:PathPart('edit')> and C<:Chained('foo')>. For the latter path give +the action just a C<:Args(1)> to mark it as 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 +C<:Chained>: + +=head2 Attributes + +=over 8 + +=item PathPart + +Sets the name of this part of the chain. If it is specified without +arguments, it takes the name of the action as default. So basically +C and C are identical. +This can also contain slashes to bind to a deeper level. An action +with C would bind to +C. If you don't specify C<:PathPart> it has the same +effect as using C<:PathPart>, it would default to the action name. + +=item Chained + +Has to be specified for every child in the chain. Possible values are +absolute and relative private action paths, with the relatives pointing +to the current controller, or a single slash C to tell Catalyst that +this is the root of a chain. The attribute C<:Chained> without aguments +also defaults to the C behaviour. + +Due to the fact that 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 every +controller or namespace you want. + +Another interesting possibility gives C<:Chained('.')>, which chains +itself to an action with the path of the current controllers 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 C. The specification of C<.> +as argument to Chained here chains the C action to an action with +the path of the current controller namespace, namely C. That +action chains directly to C, so the above chain comes out as end +product. + +=item CaptureArgs + +Also has to be specified for every part of the chain that is not an +endpoint. With this attribute Catalyst knows how many of the following +parts of the path (separated by C) this action wants to captures as +it's arguments. If it doesn't expect any, just specify C<:CaptureArgs(0)>. +The captures get passed to the action's C<@_> right after the context, +but you can also find them as array reference in +C<$c-Erequest-Ecaptures-E[$level]>. The C<$level> is the +level of the action in the chain that captured the parts of the path. + +An action that is part of a chain (read: that has a C<:Chained> attribute) +but has no C<:CaptureArgs> attribute is treated by Catalyst as a chain end. + +=item Args + +By default, endpoints receive the rest of the arguments in the path. You +can tell Catalyst through C<:Args> explicitly how many arguments your +endpoint expects, just like you can with C<:CaptureArgs>. Note that this +also influences if 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. + +You can specify an exact number of arguments like C<:Args(3)>, including +C<0>. If you just say C<:Args> without any arguments, it is the same as +leaving it out alltogether: The chain is matched independent of the number +of path parts after the endpoint. + +Just like with C<:CaptureArgs>, the arguments get passed to the action in +C<@_> after the context object. They can also be reached through +C<$c-Erequest-Earguments>. + +=back + +=head2 auto actions, dispatching and forwarding + +Note that the list of C actions called depends on the private path +of the endpoint of the chain, not on the chained actions way. The C +actions will be run before the chain dispatching begins. In every other +aspect, C actions behave as documented. + +The Cing to other actions does just what you would expect. But if +you C out of a chain, the rest of the chain will not get called +after the C returned. + =head1 AUTHOR Matt S Trout