将URI与当前URL匹配

将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:

  1. URL -> /user/.+

    result -> Hey Guest!

  2. 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

  1. Explode the $matcher as well as $url on the basis of /

  2. 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

  3. check each part of $matcher with each part of $url using preg_match.

    if all parts match return true $matcher is correct route

    if 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 :

  1. You can no longer match generic routes because now you need to explicitly define the number of parts a URL will have

  2. 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.