创建独特的页面标题 slugs php
Posted
技术标签:
【中文标题】创建独特的页面标题 slugs php【英文标题】:creating unique page title slugs php 【发布时间】:2013-04-05 00:00:01 【问题描述】:我有一个为页面标题创建唯一 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;
【问题讨论】:
您可以使用 LIKE 返回所有匹配的结果,而不是重复访问数据库,然后如果结果数大于 0,则将结果数的计数附加到 slug 中。跨度> @Hailwood 没有想到这一点。感谢您的澄清 追加计数+1 不是最好的主意,因为第 3 条记录可能已被删除,但第 4 条记录没有被删除,留下 3 条记录,您添加的下一条记录会发生冲突 【参考方案1】:只需访问数据库一次,立即获取所有内容,这可能是最大的瓶颈。
$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-slug
LIKE
也会返回诸如
my-slug-is-awesome
my-slug-is-awesome-1
my-slug-rules
等会导致问题,in_array()
检查确保我们只检查输入的确切 slug。
我们为什么不只计算结果和 +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 BY
和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)
【讨论】:
可以按字段长度排序,然后按字段本身实现自然排序:)排序。 这在以下情况下不起作用。 1) 我的帖子 -> my-post 2) 我的帖子 -> my-post-1 3) 我的 -> my-3 这个转换会出错。【参考方案2】:只需使用一个查询即可为您完成所有繁重的工作......
$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;
【讨论】:
非常感谢,完美。感谢所有做出贡献的人,你们都以某种方式提供了帮助。非常感谢。 太棒了!不要忘记将其中一个答案标记为已接受,因为这会变成大量代码繁重的答案! 不好的例子。删除一行后,它会添加双 slugs 我不认为这不是个好主意。假设第二行有 slug_2,但它从不检查它。如果相同的标题被使用了 3 次,将有 1 个标题和 2 个标题_2 s 这在以下情况下不起作用。 1) 我的帖子 -> my-post 2) 我的帖子 -> my-post-1 3) 我的 -> my-3 这个转换会出错。【参考方案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]*
表示任意数量的数字。
此查询将选择以$slug
开头且编号最大的行。
之后,您可以解析结果获取编号并增加它。
在这种情况下,您将只有一个查询和许多未使用的性能。
更新
这不起作用,因为slug-8
将比slug-11
“更大”。但不知道如何解决它。可能是ORDER BY
idDESC
?
更新 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";
【讨论】:
你试过ORDER BY LENGTH($field_name) DESC, $field_name DESC
吗?
@Jack 嗯,可能会起作用。谢谢,会更新我的答案。
虽然这不会检查原始 slug 是否存在(又名-int
),所以所有 slug 最终都会有一个 -int
还有两件事,1)你有两个集合或 ORDER BY,2)你想要 OR 不 AND,没有字符串可以是它自己和它自己 + -[digit]
@Hailwood 对此答案的更新比我的其他答案更多:)【参考方案4】:
您为什么不直接创建一个 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部分可以用trigger解决(改变表名和列名)。
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 Insert row, on duplicate: add suffix and re-insert
【讨论】:
将此与@Narek 的解决方案结合起来可能是最优雅的解决方案【参考方案5】:对于一个部分,我将创建一个对象来处理创建 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;
See it in action.
【讨论】:
哎呀,我把它弄坏了 :D eval.in/private/2759b55f18fd2f 没有什么可以阻止您的用户手动创建带有 slug 的页面'php-5', 'php-4', 'php-3', 'php-1'
这会导致代码崩溃,因为它期望数字在数组中按升序排列,没有什么说它们必须如此。
@Hailwood:哦,对了。只需通过 call_user_func_array 对结果进行 array_merge,然后使用 natsort 进行排序。 -- eval.in/private/3c8c6b27c5414d -- 甚至只使用in_array
或array_flip
和isset
我想我一定是累了,今晚我是个挑剔的混蛋;)
我会赞成,但我又打破了它:D eval.in/private/03780cbbf5d041 如果存在$slug-[digit]
,即使只是$slug
,它不会扔掉它的玩具并假设$slug
采取:D
我确实理解 OP 总是希望对蛞蝓进行编号,这也是为什么我在检查前 $slug->increase() 给它那个编号的原因。如果你不想这样,只需删除第一个增加。【参考方案6】:
我对答案并不完全满意,所以我想出了一个稍微不同的方法。
(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
)
【讨论】:
【参考方案7】:$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 关键字在单个数据库调用中执行此操作,这将减少您的执行时间。
【讨论】:
如果你有 slug "test-slug-1" 并尝试输入 slug "test",这会中断 如果你有行slug-1
、slug-3
、slug-5
并再添加一行呢?
好的建议,将修改或推迟到同期提交的Hailwoods解决方案
不应该是while(in_arr...
而不是while(!in_arr...
吗?否则你会循环直到在数组中找到一个项目?虽然我很喜欢这个解决方案!
另外,通过修改 $slug 每个循环,而不是增加你刚刚创建的 int,例如$slug-1-2-3-4-5
【参考方案8】:
您可以使用Fbeen/UniqueSlugBundle。这个 Bundle 是轻量级的,可以做它需要做的事情。
【讨论】:
以上是关于创建独特的页面标题 slugs php的主要内容,如果未能解决你的问题,请参考以下文章