两个网址是否相同?忽略参数顺序


Are two URLs identical? Ignore the param order

我有两个URL,正在寻找确定它们是否相同的最佳方法。

例:

$url1 = 'http://example.com/page.php?tab=items&msg=3&sort=title';
$url2 = 'http://example.com/page.php?tab=items&sort=title&msg=3';

在两个 URL 中,只有 sortmsg 参数被切换,因此我认为它们是相等的。但是,我不能简单地做if ( $url1 == $url2 ) { … }

我有一个 URL 列表,需要查找重复项,因此代码应该很快,因为它在循环中运行。(作为旁注:域/页面.php将始终相同,它只是通过参数查找URL。

也许是这样的?

function compare_url($url1, $url2){
  return (parse_url($url1,PHP_URL_QUERY) == parse_url($url2,PHP_URL_QUERY));
}

确定 URI 是否相同并不像听起来那么容易,尤其是当您在此处考虑查询参数时。

执行此操作的一种常见方法是使用一个函数来规范化 URL,然后比较规范化的 URI:

$url1 = 'http://example.com/page.php?tab=items&msg=3&sort=title';
$url2 = 'http://example.com/page.php?tab=items&sort=title&msg=3';
var_dump(url_nornalize($url1) == url_nornalize($url2)); # bool(true)

在这样的规范化函数中,你提出了你的要求。首先,URL 应根据规范进行规范化:

function url_nornalize($url, $separator = '&')
{
    // normalize according RFC 3986
    $url = new Net_URL2($url);
    $url->normalize();

然后,您可以处理其他规范化步骤,例如,对查询的子部分进行排序:

    // normalize query if applicable
    $query = $url->getQuery();
    if (false !== $query) {
        $params = explode($separator, $query);
        sort($params);
        $query = implode($separator, $params);
        $url->setQuery($query);
    }

可以采取其他步骤,例如删除默认参数或不允许的参数,或重复的参数等等。

最后返回规范化 URL 的字符串

    return (string) $url;
}

对参数使用数组/哈希映射也不错,我只是想展示一种替代方法。完整示例:

<?php
/**
 * http://stackoverflow.com/questions/27667182/are-two-urls-identical-ignore-the-param-order
 */
require_once 'Net/URL2.php';
function url_nornalize($url, $separator = '&')
{
    // normalize according RFC 3986
    $url = new Net_URL2($url);
    $url->normalize();
    // normalize query if applicable
    $query = $url->getQuery();
    if (false !== $query) {
        $params = explode($separator, $query);
        // remove empty parameters
        $params = array_filter($params, 'strlen');
        // sort parameters
        sort($params);
        $query = implode($separator, $params);
        $url->setQuery($query);
    }
    return (string)$url;
}
$url1 = 'http://EXAMPLE.com/p%61ge.php?tab=items&&&msg=3&sort=title';
$url2 = 'http://example.com:80/page.php?tab=items&sort=title&msg=3';
var_dump(url_nornalize($url1) == url_nornalize($url2)); # bool(true)

为了确保两个 URL 相同,我们需要比较至少 4 个元素:

  1. 方案(例如 httphttpsftp
  2. 主机,即 URL 的域名
  3. 路径,即请求的"文件"
  4. 请求的查询参数。

一些注意事项:

  • (1) 和 (2) 不区分大小写,这意味着http://example.orgHTTP://EXAMPLE.ORG 相同。
  • (3)可以有前导斜杠或尾随斜杠,应忽略:example.orgexample.org/相同
  • (4) 可以包含不同顺序的参数。
  • 我们可以安全地忽略锚文本或"片段"(#anchor查询参数之后),因为它们仅由浏览器解析。
  • URL 还可以包含端口号、用户名和密码 - 我认为我们可以忽略这些元素,因为它们很少使用,因此不需要在此处检查它们。
<小时 />

溶液:

这是一个检查所有这些细节的完整函数:

/**
 * Check if two urls match while ignoring order of params
 *
 * @param string $url1
 * @param string $url2
 * @return bool
 */
function do_urls_match( $url1, $url2 ) {
    // Parse urls
    $parts1 = parse_url( $url1 );
    $parts2 = parse_url( $url2 );
    
    // Scheme and host are case-insensitive.
    $scheme1 = strtolower( $parts1[ 'scheme' ] ?? '' );
    $scheme2 = strtolower( $parts2[ 'scheme' ] ?? '' );
    $host1 = strtolower( $parts1[ 'host' ] ?? '' );
    $host2 = strtolower( $parts2[ 'host' ] ?? '' );
    
    if ( $scheme1 !== $scheme2 ) {
        // URL scheme mismatch (http <-> https): URLs are not identical.
        return false;
    }
    
    if ( $host1 !== $host2 ) {
        // Different host (domain name): Not identical.
        return false;
    }
    
    // Remvoe leading/trailing slashes, url-decode special characters.
    $path1 = trim( urldecode( $parts1[ 'path' ] ?? '' ), '/' );
    $path2 = trim( urldecode( $parts2[ 'path' ] ?? '' ), '/' );
    if ( $path1 !== $path2 ) {
        // The request-path is different: Different URLs.
        return false;
    }
    // Convert the query-params into arrays.
    parse_str( $parts1['query'] ?? '', $query1 );
    parse_str( $parts2['query'] ?? '', $query2 );
    if ( count( $query1 ) !== count( $query2 ) ) {
        // Both URLs have a differnt number of params: They cannot match.
        return false;
    }
    // Only compare the query-arrays when params are present.
    if (count( $query1 ) > 0 ) {
        ksort( $query1 );
        ksort( $query2 );
        if ( array_diff( $query1, $query2 ) ) {
            // Query arrays have differencs: URLs do not match.
            return false;
        }
    }
    // All checks passed, URLs are identical.
    return true;
} // End do_urls_match()
<小时 />

测试用例

$base_urls = [
    'https://example.org/',
    'https://example.org/index.php?sort=asc&field=id&filter=foo',
    'http://EXAMPLE.com/p%61ge.php?tab=items&&&msg=3&sort=title',
];
$compare_urls = [
    'https://example.org/',
    'https://Example.Org',
    'https://example.org/index.php?sort=asc&&field=id&filter=foo',
    'http://example.org/index.php?sort=asc&field=id&filter=foo',
    'https://company.net/page.php?sort=asc&field=id&filter=foo',
    'https://example.org/index.php?sort=asc&&&field=id&filter=foo#anchor',
    'https://example.org/index.php?field=id&filter=foo&sort=asc',
    'http://example.com:80/page.php?tab=items&sort=title&msg=3',
];
foreach ( $base_urls as $url1 ) {
    printf( "'n'n%s", $url1 ); 
    foreach ( $compare_urls as $url2 ) {
        if (do_urls_match( $url1, $url2 )) {
            printf( "'n  [MATCHES]  %s", $url2 );
        }
    }
}
/* Output:
https://example.org/
  [MATCHES]  https://example.org/
  [MATCHES]  https://Example.Org
https://example.org/index.php?sort=asc&field=id&filter=foo
  [MATCHES]  https://example.org/index.php?sort=asc&&field=id&filter=foo
  [MATCHES]  https://example.org/index.php?sort=asc&&&field=id&filter=foo#anchor
  [MATCHES]  https://example.org/index.php?field=id&filter=foo&sort=asc
http://EXAMPLE.com/p%61ge.php?tab=items&&&msg=3&sort=title
  [MATCHES]  http://example.com:80/page.php?tab=items&sort=title&msg=3
*/