将URI与当前URL匹配
I made a simple router system and I'm trying to match the current uri with an url, check:
$listUri = "transaction/.+";
$uri = isset($_REQUEST['uri']) ? $_REQUEST['uri'] : '/'; // transaction/19-02-2016
if(preg_match("#^$listUri$#", $uri))
{
echo "done!";
}
now I see correctly the echo "done!";
but suppose that I put that I've this situation:
$listUri = "transaction/.+";
$uri = isset($_REQUEST['uri']) ? $_REQUEST['uri'] : '/'; // transaction/19-02-2016/SomeWrongUrlRequest?
if(preg_match("#^$listUri$#", $uri))
{
echo "done!";
}
the echo "done!"
is also printed... and this is bad. I've mapped the url like so: "/transaction/.+";
where .+
is the parameter 19-02-2016
after it, if there is more content after the .+
the request must be incorrect.
UPDATE:
In other word:
$uri => "transaction/19-02-2016/SomeWrongUrlRequest"
$listUri => "transaction/.+"
if $listUri
have transaction/.+
I must have this:
`transaction/19-02-2016/` (correct with or without final slash)
`transaction/19-02-2016/SomeWrongUrlRequest` (incorrect - there is only a .+ it would have been correct if $listUri had been: transaction/.+/SomeWrongUrlRequest)
so I have to make the match with the contents equal to the current URI
From what I see you are making(or already made) a routing system.
But I feel that the problem here won't be fixed by finding the correct regular expression but I think would be solved by finding the correct approach to create your routing system.
You need to have some sort of a policy which will determine which route should be executed when a particular URL is hit. To make my self clear Say if a programmer wants the following scenario using your router:
-
URL ->
/user/.+
result -> Hey Guest!
-
URL ->
/user/.+/{regex_matching_username}
result -> Hey Username!
Now how will the routing system decide which URL to go to if the URL is like /user/free/john
? Similar to your case, the URL #1 will still match this URL and it will keep saying Hey Guest!
.
So we need to define a priority with which a route should be executed, this can be the order in which they are defined(so routes are stored in a stack or a queue) or maybe some sort of a priority value assigned to each route(routes in a priority queue) .
Having worked with ZF1 and Laravel, I can tell about the approaches they take:
ZF1 clearly mentions that
Note: Reverse Matching
Routes are matched in reverse order so make sure your most generic routes are defined first.
So if you define a generic route like user/.+
in the last in ZF1, all of your other routes won't work.
In Laravel although I wasn't able to find it in the docs, but they seem to follow the order in which the route was defined. I am pasting in an example just in case you would like to have a look.
// matches a url that has username starting with a
Route::get('user/{name}', ['as' => 'profile', function()
{
//
echo ' I am specific';
}])->where('name', 'a.+');;
Route::get('user/{ame}', ['as' => 'profile', function()
{
//
echo ' I am generic';
}])->where('ame', '.+');
Url -> # /user/abc
Output -> I am specific
Url -> # /user/bbc
Output -> I am generic
Things work as expected, but now reversing the order of specific and generic routes
Route::get('user/{ame}', ['as' => 'profile', function()
{
//
echo ' I am generic';
}])->where('ame', '.+');
// matches a url that has username starting with a
Route::get('user/{name}', ['as' => 'profile', function()
{
//
echo ' I am specific';
}])->where('name', 'a.+');;
Url -> # /user/abc
Output -> I am generic
Url -> # /user/bbc
Output -> I am generic
Now as a generic route was at top both the URLs lead to the same output.
Having said above, you may still satisfy your specific case by breaking down regular expression as well as the URL on the basis of /
and then matching each non-empty part of both the strings. This in psuedo-code might look like this
Matching $matcher string with the current URL
Explode the $matcher as well as $url on the basis of
/
-
Check if the number of non-empty parts of both $matcher & $url are equal.
if yes continue to step 3
if no
return false
$matcher doesn't match -
check each part of $matcher with each part of $url using preg_match.
if all parts match
return true
$matcher is correct routeif any one part doesn't match
return false
$matcher is not the corrct route.
I hope all this made sense :)
Update: Adding some code for the psuedocode mentioned above
function matchRoute($url, $pattern) {
// get parts of the url
$urlParts = array_filter(explode('/', $url));
$patternParts = array_filter(explode('/', $pattern));
// match if number of parts are equal
if (count($urlParts) != count($patternParts)) {
return false;
}
// preg match in a loop
for ($i = 0 ; $i < count($urlParts); $i++) {
if(!preg_match('/' . $patternParts[$i] .'/', $urlParts[$i])) {
return false;
}
}
return true;
}
$testUri = 'transaction/19-02-2016/SomeWrongUrlRequest';
$matchUri = 'transaction/.+';
echo "expected false
";
var_dump(matchRoute($testUri, $matchUri));
echo "expected true
";
var_dump(matchRoute('transaction/19-02-2016', $matchUri));
echo "expected true
";
var_dump(matchRoute('transaction/19-02-2016/', $matchUri));
echo "expected true
";
var_dump(matchRoute($testUri, 'transaction/.+/SomeWrongUrlRequest'));
echo "expected false
";
var_dump(matchRoute($testUri, 'transaction/.+/SomeOtherUrlRequest'));
Output:
expected false
bool(false)
expected true
bool(true)
expected true
bool(true)
expected true
bool(true)
expected false
bool(false)
Now the code written above would not be the best solution for this, some of the issues I can see right away are :
You can no longer match generic routes because now you need to explicitly define the number of parts a URL will have
array_filter
checks for non-empty value so it will exclude a part such as/0
. (although this can be handled by using a custom callback)
Use the above if you are sure your scenarios are fullfilled by it.