У меня есть функция для создания уникального слитка для названия страницы. Он проверяет, доступен ли slug в таблице страниц, а затем создает уникальный слизню, добавив соответственно «-int». Функция работает отлично для первых трех записей, например, для «тестовой пули», введенной три раза, создаст «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;
}
Просто используйте один запрос, чтобы сделать весь тяжелый подъем для вас …
$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()
как если бы slug был my-slug
LIKE
также возвращал строки, такие как
my-slug-is-awesome my-slug-is-awesome-1 my-slug-rules
и т. д., что вызовет проблемы, проверки in_array()
гарантируют, что мы проверяем только точный введённый пул.
Это связано с тем, что если вы получили несколько результатов и удалили несколько, ваш следующий слизень вполне может конфликтовать.
Например
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)
Вы можете просто выбрать 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
id 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";
Для одной части я создал бы объект, который имеет дело с частью, создающей 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; }
Но, как уже писали другие, вы должны сначала получить все слитки, которые нумеруются сразу из базы данных, а затем при необходимости выбрать уникальный. Это уменьшит количество вызовов базы данных. Также код намного компактнее:
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 . Этот комплект легкий и делает то, что ему нужно.