Skip site navigation (1)Skip section navigation (2)

FreeBSD Manual Pages

  
 
  

home | help
Catalyst::RouteMatchinUser Contributed Perl DocumentCatalyst::RouteMatching(3)

Name
       Catalyst::RouteMatching - How Catalyst maps an incoming URL to actions
       in controllers.

Description
       This is a WIP document intended to help people understand the logic
       that Catalyst uses to determine how to match in incoming	request	to an
       action (or action chain)	in a controller.

   Request to Controller/Action	Matching
       Catalyst	maps requests to action	using a	'longest path wins' approach.
       That means that if the request is '/foo/bar/baz'	That means the action
       'baz' matches:

	   package MyApp::Controller::Foo;

	   use Moose;
	   use MooseX::MethodAttributes

	   extends 'Catalyst::Controller';

	   sub bar :Path('bar')	Args(1)	{ ...}
	   sub baz :Path('bar/baz') Args(0) { ... }

       Path length matches take	precedence over	all other types	of matches
       (included HTTP Method, Scheme, etc.).  The same holds true for Chained
       actions.	 Generally the chain that matches the most PathParts wins.

   Args(N) versus Args
       'Args' matches any number of args.  Because this	functions as a sort of
       catchall, we treat 'Args' as the	lowest precedence of any Args(N) when
       N is 0 to infinity.  An action with 'Args' always get the last chance
       to match.

   When	two or more actions match a given Path
       Sometimes two or	more actions match the same path and all have the same
       PathPart	length.	 For example:

	   package MyApp::Controller::Root;

	   use Moose;
	   use MooseX::MethodAttributes

	   extends 'Catalyst::Controller';

	   sub root :Chained(/)	CaptureArgs(0) { }

	     sub one :Chained(root) PathPart('') Args(0) { }
	     sub two :Chained(root) PathPart('') Args(0) { }
	     sub three :Chained(root) PathPart('') Args(0) { }

	   __PACKAGE__->meta->make_immutable;

       In this case the	last defined action wins (for the example that is
       action 'three').

       This is most common to happen when you are using	action matching	beyond
       paths, such as when using method	matching:

	   package MyApp::Controller::Root;

	   use Moose;
	   use MooseX::MethodAttributes

	   extends 'Catalyst::Controller';

	   sub root :Chained(/)	CaptureArgs(0) { }

	     sub any :Chained(root) PathPart('') Args(0) { }
	     sub get :GET Chained(root)	PathPart('') Args(0) { }

	   __PACKAGE__->meta->make_immutable;

       In the above example GET	/root could match both actions.	 In this case
       you should define your 'catchall' actions higher	in the controller.

   Type	Constraints in Args and	Capture	Args
       Beginning in Version 5.90090+ you may use Moose,	MooseX::Types or
       Type::Tiny type constraints to further declare allowed matching for
       Args or CaptureArgs.  Here is a simple example:

	   package MyApp::Controller::User;

	   use Moose;
	   use MooseX::MethodAttributes;
	   use MooseX::Types::Moose qw(Int);

	   extends 'Catalyst::Controller';

	   sub find :Path('') Args(Int)	{
	     my	($self,	$c, $int) = @_;
	   }

	   __PACKAGE__->meta->make_immutable;

       In this case the	incoming request "http://localhost:/user/100" would
       match the action	"find" but "http://localhost:/user/not_a_number" would
       not. You	may find declaring constraints in this manner aids with
       debugging, automatic generation of documentation	and reducing the
       amount of manual	checking you might need	to do in your actions.	For
       example if the argument in the given action was going to	be used	to
       lookup a	row in a database, if the matching field expected an integer,
       a string	might cause a database exception, prompting you	to add
       additional checking of the argument prior to using it.  In general it
       is hoped	this feature can lead to reduced validation boilerplate	and
       more easily understood and declarative actions.

       More than one argument may be added by comma separating your type
       constraint names, for example:

	   use Types::Standard qw/Int Str/;

	   sub find :Path('') Args(Int,Int,Str)	{
	     my	($self,	$c, $int1, $int2, $str)	= @_;
	   }

       Would require three arguments, an integer, integer and a	string.	 Note
       in this example we constrained the args using imported types via
       Types::Standard.	 Although you may use stringy Moose types, we
       recommend imported types	since this is less ambiguous to	your readers.
       If you want to use Moose	stringy	types. you must	quote them (either
       "Int" or	'Int' is fine).

       Conversely, you should not quote	types that are imported!

       Using type constraints in a controller

       By default Catalyst allows all the standard, built-in, named type
       constraints that	come bundled with Moose.  However it is	trivial	to
       create your own Type constraint libraries and export them to a
       controller that wishes to use them.  We recommend using Type::Tiny or
       MooseX::Types for this.	Here is	an example using some extended type
       constraints via the Types::Standard library that	is packaged with
       Type::Tiny:

	   package MyApp::Controller::User;

	   use Moose;
	   use MooseX::MethodAttributes;
	   use Types::Standard qw/StrMatch Int/;

	   extends 'Catalyst::Controller';

	   sub looks_like_a_date :Path('') Args(StrMatch[qr{\d\d-\d\d-\d\d}]) {
	     my	($self,	$c, $int) = @_;
	   }

	   __PACKAGE__->meta->make_immutable;

       This would match	URLs like "http://localhost/user/11-11-2015" for
       example.	 If you've been	missing	the old	RegExp matching, this can
       emulate a good chunk of that ability, and more.

       A tutorial on how to make custom	type libraries is outside the scope of
       this document.  I'd recommend looking at	the copious documentation in
       Type::Tiny or in	MooseX::Types if you prefer that system.  The author
       recommends Type::Tiny if	you are	unsure which to	use.

       Type constraint namespace.

       By default we assume the	namespace which	defines	the type constraint is
       in the package which contains the action	declaring the arg or capture
       arg.  However if	you do not wish	to import type constraints into	you
       package,	you may	use a fully qualified namespace	for your type
       constraint.  If you do this you must install Type::Tiny which defines
       the code	used to	lookup and normalize the various types of Type
       constraint libraries.

       Example:

	   package MyApp::Example;

	   use Moose;
	   use MooseX::MethodAttributes;

	   extends 'Catalyst::Controller';

	   sub an_int_ns :Local	Args(MyApp::Types::Int)	{
	     my	($self,	$c, $int) = @_;
	     $c->res->body('an_int (withrole)');
	   }

       Would basically work the	same as:

	   package MyApp::Example;

	   use Moose;
	   use MooseX::MethodAttributes;
	   use MyApp::Types 'Int';

	   extends 'Catalyst::Controller';

	   sub an_int_ns :Local	Args(Int) {
	     my	($self,	$c, $int) = @_;
	     $c->res->body('an_int (withrole)');
	   }

       namespace::autoclean

       If you want to use namespace::autoclean in your controllers you must
       'except'	imported type constraints since	the code that resolves type
       constraints in args / capture args run after the	cleaning.  For
       example:

	   package MyApp::Controller::Autoclean;

	   use Moose;
	   use MooseX::MethodAttributes;
	   use namespace::autoclean -except => 'Int';
	   use MyApp::Types qw/Int/;

	   extends 'Catalyst::Controller';

	   sub an_int :Local Args(Int) {
	     my	($self,	$c, $int) = @_;
	     $c->res->body('an_int (autoclean)');
	   }

       Using roles and base controller with type constraints

       If your controller is using a base class	or a role that has an action
       with a type constraint you should declare your use of the type
       constraint in that role or base controller in the same way as you do in
       main controllers.  Catalyst will	try to find the	package	with declares
       the type	constraint first by looking in any roles and then in
       superclasses.  It will use the first package that defines the type
       constraint.  For	example:

	   package MyApp::Role;

	   use Moose::Role;
	   use MooseX::MethodAttributes::Role;
	   use MyApp::Types qw/Int/;

	   sub an_int :Local Args(Int) {
	     my	($self,	$c, $int) = @_;
	     $c->res->body('an_int (withrole)');
	   }

	   sub an_int_ns :Local	Args(MyApp::Types::Int)	{
	     my	($self,	$c, $int) = @_;
	     $c->res->body('an_int (withrole)');
	   }

	   package MyApp::BaseController;

	   use Moose;
	   use MooseX::MethodAttributes;
	   use MyApp::Types qw/Int/;

	   extends 'Catalyst::Controller';

	   sub from_parent :Local Args(Int) {
	     my	($self,	$c, $id) = @_;
	     $c->res->body('from_parent	$id');
	   }

	   package MyApp::Controller::WithRole;

	   use Moose;
	   use MooseX::MethodAttributes;

	   extends 'MyApp::BaseController';

	   with	'MyApp::Role';

       If you have complex controller hierarchy, we do not at this time
       attempt to look for all packages	with a match type constraint, but
       instead take the	first one found.  In the future	we may add code	that
       attempts	to insure a sane use of	subclasses with	type constraints but
       right now there are no clear use	cases so report	issues and interests.

       Match order when	more than one Action matches a path.

       As previously described,	Catalyst will match 'the longest path',	which
       generally means that named path / path_parts will take precedence over
       Args or CaptureArgs.  However, what will	happen if two actions match
       the same	path with equal	args?  For example:

	   sub an_int :Path(user) Args(Int) {
	   }

	   sub an_any :Path(user) Args(1) {
	   }

       In this case Catalyst will check	actions	starting from the LAST one
       defined.	 Generally this	means you should put your most specific	action
       rules LAST and your 'catch-alls'	first.	In the above example, since
       Args(1) will match any argument,	you will find that that	'an_int'
       action NEVER gets hit.  You would need to reverse the order:

	   sub an_any :Path(user) Args(1) {
	   }

	   sub an_int :Path(user) Args(Int) {
	   }

       Now requests that match this path would first hit the 'an_int' action
       and will	check to see if	the argument is	an integer.  If	it is, then
       the action will execute,	otherwise it will pass and the dispatcher will
       check the next matching action (in this case we fall through to the
       'an_any'	action).

       Type Constraints	and Chained Actions

       Using type constraints in Chained actions works the same	as it does for
       Path and	Local or Global	actions.  The only difference is that you may
       declare type constraints	on CaptureArgs as well as Args.	 For Example:

	 use Types::Standard qw/Int Tuple/;

	 sub chain_base	:Chained(/) CaptureArgs(1) { }

	   sub any_priority_chain :GET Chained(chain_base) PathPart('')	Args(1)	{  }

	   sub int_priority_chain :Chained(chain_base) PathPart('') Args(Int) {	 }

	   sub link_any	:Chained(chain_base) PathPart('') CaptureArgs(1) { }

	     sub any_priority_link_any :Chained(link_any) PathPart('') Args(1) {  }

	     sub int_priority_link_any :Chained(link_any) PathPart('') Args(Int) {  }

	   sub link_int	:Chained(chain_base) PathPart('') CaptureArgs(Int) { }

	     sub any_priority_link :Chained(link_int) PathPart('') Args(1) {  }

	     sub int_priority_link :Chained(link_int) PathPart('') Args(Int) {	}

	   sub link_int_int :Chained(chain_base) PathPart('') CaptureArgs(Int,Int) { }

	     sub any_priority_link2 :Chained(link_int_int) PathPart('')	Args(1)	{  }

	     sub int_priority_link2 :Chained(link_int_int) PathPart('')	Args(Int) {  }

	   sub link_tuple :Chained(chain_base) PathPart('') CaptureArgs(Tuple[Int,Int,Int]) { }

	     sub any_priority_link3 :Chained(link_tuple) PathPart('') Args(1) {	 }

	     sub int_priority_link3 :Chained(link_tuple) PathPart('') Args(Int)	{  }

       These chained actions might create match	tables like the	following:

	   [debug] Loaded Chained actions:
	   .-------------------------------------+--------------------------------------.
	   | Path Spec				 | Private				|
	   +-------------------------------------+--------------------------------------+
	   | /chain_base/*/*			 | /chain_base (1)			|
	   |					 | => GET /any_priority_chain (1)	|
	   | /chain_base/*/*/*			 | /chain_base (1)			|
	   |					 | -> /link_int	(Int)			|
	   |					 | => /any_priority_link (1)		|
	   | /chain_base/*/*/*/*		 | /chain_base (1)			|
	   |					 | -> /link_int_int (Int,Int)		|
	   |					 | => /any_priority_link2 (1)		|
	   | /chain_base/*/*/*/*/*		 | /chain_base (1)			|
	   |					 | -> /link_tuple (Tuple[Int,Int,Int])	|
	   |					 | => /any_priority_link3 (1)		|
	   | /chain_base/*/*/*			 | /chain_base (1)			|
	   |					 | -> /link_any	(1)			|
	   |					 | => /any_priority_link_any (1)	|
	   | /chain_base/*/*/*/*/*/*		 | /chain_base (1)			|
	   |					 | -> /link_tuple (Tuple[Int,Int,Int])	|
	   |					 | -> /link2_int (UserId)		|
	   |					 | => GET /finally (Int)		|
	   | /chain_base/*/*/*/*/*/...		 | /chain_base (1)			|
	   |					 | -> /link_tuple (Tuple[Int,Int,Int])	|
	   |					 | -> /link2_int (UserId)		|
	   |					 | => GET /finally2 (...)		|
	   | /chain_base/*/*			 | /chain_base (1)			|
	   |					 | => /int_priority_chain (Int)		|
	   | /chain_base/*/*/*			 | /chain_base (1)			|
	   |					 | -> /link_int	(Int)			|
	   |					 | => /int_priority_link (Int)		|
	   | /chain_base/*/*/*/*		 | /chain_base (1)			|
	   |					 | -> /link_int_int (Int,Int)		|
	   |					 | => /int_priority_link2 (Int)		|
	   | /chain_base/*/*/*/*/*		 | /chain_base (1)			|
	   |					 | -> /link_tuple (Tuple[Int,Int,Int])	|
	   |					 | => /int_priority_link3 (Int)		|
	   | /chain_base/*/*/*			 | /chain_base (1)			|
	   |					 | -> /link_any	(1)			|
	   |					 | => /int_priority_link_any (Int)	|
	   '-------------------------------------+--------------------------------------'

       As you can see the same general path could be matched by	various	action
       chains.	In this	case the rule described	in the previous	section	should
       be followed, which is that Catalyst will	start with the last defined
       action and work upward.	For example the	action "int_priority_chain"
       would be	checked	before "any_priority_chain".  The same applies for
       actions that are	midway links in	a longer chain.	 In this case
       "link_int" would	be checked before "link_any".  So as always we
       recommend that you place	you priority or	most constrained actions last
       and you least or	catch-all actions first.

       Although	this reverse order checking may	seen counter intuitive it does
       have the	added benefit that when	inheriting controllers any new actions
       added would take	check precedence over those in your parent controller
       or consumed role.

       Please note that	your declared type constraint names will now appear in
       the debug console.

Author
       John Napiorkowski jjnapiork@cpan.org <email:jjnapiork@cpan.org>

perl v5.24.1			  2015-10-29	    Catalyst::RouteMatching(3)

Name | Description | Author

Want to link to this manual page? Use this URL:
<https://www.freebsd.org/cgi/man.cgi?query=Catalyst::RouteMatching&sektion=3&manpath=FreeBSD+12.0-RELEASE+and+Ports>

home | help