8

我有一个为页面标题创建唯一 slug 的功能。它检查页面表中是否有可用的 slug,然后通过相应地添加一个“-int”来创建一个唯一的 slug。该功能适用​​于前三个条目,例如,“test slug”输入三次将创建“test-slug-1”、“test-slug-2”和“test-slug-3”。然后,我收到第四个条目的错误“致命错误:超过 30 秒的最大执行时间”。逻辑应该有问题,谁能帮我找到它。下面是代码:

function createSlug($title, $table_name, $field_name) {

global $db_connect;

$slug = preg_replace("/-$/","",preg_replace('/[^a-z0-9]+/i', "-", strtolower($title)));

$counter = 1;

do{

  $query = "SELECT * FROM $table_name WHERE  $field_name  = '".$slug."'";
  $result = mysqli_query($db_connect, $query) or die(mysqli_error($db_connect));


  if(mysqli_num_rows($result) > 0){
      $count = strrchr($slug , "-"); 
      $count = str_replace("-", "", $count);
      if($count > 0){

          $length = count($count) + 1;
          $newSlug = str_replace(strrchr($slug , "-"), '',$slug);
          $slug = $newSlug.'-'.$length;

          $count++;

      }else{
          $slug = $slug.'-'.$counter;
      }  

  }

  $counter++; 
  $row = mysqli_fetch_assoc($result);

}while(mysqli_num_rows($result) > 0);

return $slug;

}

4

8 回答 8

33

只需访问数据库一次,立即获取所有内容,这可能是最大的瓶颈。

$query = "SELECT * FROM $table_name WHERE  $field_name  LIKE '".$slug."%'";

然后把你的结果放在一个数组中(比如说$slugs

//we only bother doing this if there is a conflicting slug already
if(mysqli_num_rows($result) !== 0 && in_array($slug, $slugs)){
    $max = 0;

    //keep incrementing $max until a space is found
    while(in_array( ($slug . '-' . ++$max ), $slugs) );

    //update $slug with the appendage
    $slug .= '-' . $max;
}

我们使用in_array()检查,就好像 slug 也my-slugLIKE返回行,例如

my-slug-is-awesome
my-slug-is-awesome-1
my-slug-rules

等会导致问题,in_array()检查确保我们只检查输入的确切信息。

为什么我们不只计算结果和+1?

这是因为如果您有多个结果并删除了一些结果,那么您的下一个 slug 很可能会发生冲突。

例如

my-slug
my-slug-2
my-slug-3
my-slug-4
my-slug-5

删除 -3 和 -5 给我们留下

my-slug
my-slug-2
my-slug-4

因此,这给了我们 3 个结果,下一个插入将是my-slug-4已经存在的。

为什么我们不只使用ORDER BYand LIMIT 1

我们不能只order by在查询中做一个,因为缺乏自然排序会使my-slug-10排名低于my-slug-4逐个字符比较而4高于1

例如

m = m
y = y
- = -
s = s
l = l
u = u
g = g
- = -
4 > 1 !!!
  < 0 (But the previous number was higher, so from here onwards is not compared)
于 2013-04-12T12:57:38.870 回答
16

只需使用一个查询即可为您完成所有繁重的工作......

$slug = preg_replace("/-$/","",preg_replace('/[^a-z0-9]+/i', "-", strtolower($title)));

$query = "SELECT COUNT(*) AS NumHits FROM $table_name WHERE  $field_name  LIKE '$slug%'";
$result = mysqli_query($db_connect, $query) or die(mysqli_error($db_connect));
$row = $result->fetch_assoc();
$numHits = $row['NumHits'];

return ($numHits > 0) ? ($slug . '-' . $numHits) : $slug;
于 2013-04-12T12:55:51.380 回答
3

您可以只选择具有最大数字的 slug 并将其增加 1:

$query = "SELECT $field_name FROM $table_name WHERE  $field_name LIKE '".$slug."-[0-9]*' ORDER BY $field_name DESC LIMIT 1";

[0-9]*in query 表示任何数字计数。

此查询将选择$slug开头和最大数字的行。

之后,您可以解析结果获取编号并增加它。

在这种情况下,您将只有一个查询和许多未使用的性能。

更新

这行不通,因为slug-8会比slug-11. 但不知道如何解决它。也许ORDER BY身份证DESC

更新 2

查询也可以按长度排序,它会正常工作。感谢杰克:

$query = "SELECT $field_name FROM $table_name WHERE  $field_name LIKE '".$slug."-[0-9]*' ORDER BY LENGTH($field_name), $field_name DESC LIMIT 1";

更新 3

还增加了对原始蛞蝓的检查。感谢海尔伍德。

$query = "SELECT $field_name FROM $table_name WHERE $field_name = '".$slug."' OR $field_name LIKE '".$slug."-[0-9]*' ORDER BY LENGTH($field_name), $field_name DESC LIMIT 1";
于 2013-04-12T12:52:46.060 回答
2

为什么不直接创建一个 slug 并将涉及到 MySQL 索引的其余工作留给我们。这是一个slugify函数(它是 Symfony 框架使用的稍作修改的版本)。

function slugify( $text ) {
    $text = preg_replace('~[^\\pL\d]+~u', '-', $text);  
    $text = trim($text, '-');
    $text = iconv('utf-8', 'ASCII//IGNORE//TRANSLIT', $text);   
    $text = strtolower(trim($text));
    $text = preg_replace('~[^-\w]+~', '', $text);

    return empty($text) ? substr( md5( time() ), 0, 8 ) : $text;
}

并且 MySQL 部分可以通过触发器解决(更改表和列名)。

BEGIN
     declare original_slug varchar(255);
     declare slug_counter int;
     set original_slug = new.slug;
     set slug_counter = 1;
     while exists (select true from post where slug = new.slug) do
        set new.slug = concat(original_slug, '-', slug_counter); 
        set slug_counter = slug_counter + 1;
     end while;
END

MySQL插入行,重复:添加后缀并重新插入

于 2015-07-23T18:55:03.593 回答
1

对于一个部分,我将创建一个对象来处理创建 slug 和处理数字的部分:

// generate new slug:
$slug = new NumberedSlug('Creating Unique Page Title Slugs in PHP');
echo $slug, "\n", $slug->increase(), "\n";

// read existing slug:
$slug = new NumberedSlug('creating-unique-page-title-slugs-in-php-44');
echo $slug->getNumber(), "\n";

输出:

creating-unique-page-title-slugs-in-php
creating-unique-page-title-slugs-in-php-1
44

对于另一部分,数据库,这已经大大简化了您的代码(请仔细检查,我已经快速完成了)。另请参阅如何从您实际拥有的 Mysqli 对象中受益(但不要按原样使用):

function createSlug($title, $table_name, $field_name, Mysqli $mysqli = NULL)
{
    $mysqli || $mysqli = $GLOBALS['db_connect'];

    $slug = new NumberedSlug($title);

    do
    {
        $query = "SELECT 1 FROM $table_name WHERE  $field_name  = '" . $slug . "'";

        if (!$result = $mysqli->query($query)) {
            throw new RuntimeException(var_export($mysqli->error_list, true));
        }

        if ($result->num_rows) {
            $slug->increase();
        }

    } while ($result->num_rows);

    return $slug;
}

但是正如其他人已经写的那样,您应该首先从数据库中获取所有同时编号的 slug,然后在必要时选择一个唯一的。这将减少数据库调用的次数。代码也更加紧凑:

function createSlug2($title, $table_name, $field_name, Mysqli $mysqli = NULL)
{
    $mysqli || $mysqli = $GLOBALS['db_connect'];

    $slug = new NumberedSlug($title);

    $query = "SELECT $field_name FROM $table_name WHERE $field_name LIKE '$slug-_%'";

    if (!$result = $mysqli->query($query)) {
        throw new RuntimeException(var_export($mysqli->error_list, true));
    }

    $existing = array_flip(call_user_func_array('array_merge', $result->fetch_all()));

    $slug->increase();

    while (isset($existing[$slug])) 
    {
        $slug->increase();
    }

    return $slug;
}

在行动中看到它。

于 2013-04-12T14:06:11.160 回答
1

我对答案并不完全满意,所以我想出了一个稍微不同的方法。

(SELECT CONCAT({$slug}, '-', counter) FROM (
  SELECT (@row_number:=@row_number + 1) AS counter, ev.*
  FROM (
    SELECT REPLACE(slug, {$slug}-, '') AS remainder
    FROM products, (SELECT @row_number:=0) AS t
    WHERE slug LIKE '{$slug}%'
  ) ev
  ORDER BY LENGTH(remainder), remainder
) sr
WHERE counter <> remainder)
LIMIT 1

这基本上是做什么的,它检查数据库中与新 slug 相似的所有现有值,并将其与行号匹配以检查间隙,如果没有找到,它使用第一个生成的最大数相同的 slug 被推到最后(注意:我们替换slug-而不是slug

于 2020-12-11T17:26:43.403 回答
0
$query = "SELECT * FROM $table_name WHERE  $field_name  LIKE '".$slug."%'";
$result = mysqli_query($db_connect, $query) or die(mysqli_error($db_connect));
//EDITED BASED ON COMMENT SUGGESTIONS
//create array of all matching slug names currently in database
$slugs = array();
while($row = $result->fetch_row()) {
    $slugs[] = $row['field_name'];
}
//test if slug is in database, append - '1,2,..n' until available slug is found
if(in_array($slug, $slugs)){
    $count = 1;
    do{
       $testSlug = $slug . '-' . $count;
       $count++;
    } while(in_array($testSlug, $slugs));
    $slug = $testSlug;
}
//insert slug 

您应该能够使用 LIKE 关键字在单个数据库调用中执行此操作,这将减少您的执行时间。

于 2013-04-12T12:55:32.513 回答
0

您可以使用Fbeen/UniqueSlugBundle。这个 Bundle 是轻量级的,可以做它需要做的事情。

于 2015-09-23T14:36:08.480 回答