создание уникального заголовка страницы слагает php
У меня есть функция для создания уникального заголовка для заголовка страницы. Он проверяет, доступен ли слаг в таблице страниц, затем создает уникальный слаг, добавляя соответственно "-int". Функция отлично работает для первых трех записей, например, для "тестового слизняка", введенного три раза, создаст "тестовый слизняк-1", "тестовый слизняк-2" и "тестовый слизняк-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;
}
7 answers
Просто используйте один запрос, чтобы выполнить всю тяжелую работу за вас...
$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;
Просто зайдите в базу данных один раз, захватите все сразу, скорее всего, это самое большое узкое место.
$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()
, как если бы пуля была my-slug
, LIKE
также возвращала бы строки, такие как
my-slug-is-awesome
my-slug-is-awesome-1
my-slug-rules
И т.д., Которые могли бы вызвать проблемы, проверки in_array()
гарантируют, что мы проверяем только точный введенный слиток.
Почему бы нам просто не подсчитать результаты и +1?
Это потому, что если у вас было несколько результатов, и вы удалили несколько, ваш следующий слиток вполне может конфликтовать.
Например,
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:
$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
идентификатор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";
Для одной части я бы создал объект, который имеет дело с частью, создающей слизняк и обрабатывающей число:
// 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;
}
Но, как уже писали другие, вы должны сначала получить все пули, которые пронумерованы сразу из базу данных, а затем при необходимости выберите уникальную. Это сократит количество вызовов базы данных. Кроме того, код намного компактнее:
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;
}
$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, что сократит время выполнения.
Почему бы вам просто не создать слизняк и не оставить остальную часть работы, которая включает индексирование в 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, при дублировании: добавьте суффикс и повторно вставьте
Вы могли бы использовать Fbeen/uniqueslugbundle. Этот пакет легкий и делает то, что ему нужно.