what is the best way to hanlde optional url argume

2019-06-08 13:45发布

问题:

For example:

I know how to match www.domain.com/foo/21

sub foo : Path('/foo') Args(1) {
  my ( $self, $c, $foo_id ) = @_;
  # do stuff with foo
}

But how can I match www.domain.com/foo/21 OR www.domain.com/foo/21/bar/56 ?

sub foo : <?> {
  my ( $self, $c, $foo_id, $bar_id ) = @_;
  # do stuff with foo, and maybe do some things with bar if present
}

Thanks

Update: Following Daxim's suggestion, I tried to use :Regex

sub foo : Regex('foo/(.+?)(?:/bar/(.+))?') {
   my ( $self, $c ) = @_;
   my ( $foo_id, $bar_id ) = @{ $c->req->captures };
}

But this doesn't seem to work; the url is matched, but $bar_id is always undef. If I remove the optional opperator from the end of the regex then it does capture $bar_id correctly, but then both foo and bar must be present to get a url match. I'm not sure if this is a perl regex issue, or a Catalyst issue. Any ideas?

Update:

As Daxim points out, its a regex issue. I can't see why the above regex doesn't work, but I did manage to find one that does:

sub foo : Regex('foo/([^/]+)(?:/bar/([^/]+))?') {
   my ( $self, $c ) = @_;
   my ( $foo_id, $bar_id ) = @{ $c->req->captures };
}

(I didn't use \d+ in the captures like Daxim did as my ids might not be numeric)

Thanks all for the help and suggestions, I learnt a lot about handling urls in Catalyst :D

回答1:

See item Pattern-match (:Regex and :LocalRegex) in Catalyst::Manual::Intro#Action_types.


nick writes:

I'm not sure if this is a perl regex issue, or a Catalyst issue. Any ideas?

How about simply trying it out?

repl>>> $_ = '/foo/21/bar/56'
/foo/21/bar/56

repl>>> m|foo/(\d+)(?:/bar/(\d+))?|
$VAR1 = 21;
$VAR2 = 56;

repl>>> $_ = '/foo/21'
/foo/21

repl>>> m|foo/(\d+)(?:/bar/(\d+))?|
$VAR1 = 21;
$VAR2 = undef;


回答2:

The Args attribute doesn't have to be limited to a specific number of arguments. For instance, the following should work:

sub foo :Args() {    # matches /foo, /foo/123, /foo/123/bar/456, /foo/123/bar/456/*
  my($self, $c, $foo_id, %optional_params) = @_;
  if($optional_params{bar}){
    # ...
  }
}

Keep in mind that all of the remaining URL segments after the path prefix and action name will be present in @remainder. Also, since you're not specifying how many arguments you need, Catalyst will allow a URL without any args to match this action. Validate your input accordingly!

UPDATED with :Chained example

The following (untested) catalyst actions would provide you with a little more strict action matching that you seem to be looking for. The downside is that you must rely on the stash to share data between all the actions.

sub foo :Chained('/') :PathPart :CaptureArgs(1) {
  my($self, $c, $foo_id) = @_;
  $c->stash->{foo_id} = $foo_id; # or whatever
}

sub foo_no_bar :Chained('foo') :Args(0) {
  my($self, $c) = @_;
  # matches on /foo/123 but not /foo/123/bar/456
  my $foo_id = $c->stash->{foo_id}; 
}

sub bar :Chained('foo') :PathPart :Args(1) {
  my($self, $c, $bar_id) = @_;
  my $foo_id = $c->stash->{foo_id};
  # matches on /foo/123/bar/456 but not /foo/123 or /foo/123/baz/456
}


标签: perl catalyst