создание уникального заголовка страницы слагает 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;

}

Author: bStaq, 2013-04-12

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;
 8
Author: Nerdwood, 2013-04-12 12:55:51

Просто зайдите в базу данных один раз, захватите все сразу, скорее всего, это самое большое узкое место.

$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)
 30
Author: Hailwood, 2013-04-12 14:28:04

Вы можете просто выбрать слизняка с наибольшим числом и увеличить его на 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";
 1
Author: Narek, 2013-04-12 13:48:10

Для одной части я бы создал объект, который имеет дело с частью, создающей слизняк и обрабатывающей число:

// 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;
}

Посмотрите на это в действии.

 1
Author: M8R-1jmw5r, 2013-04-12 14:30:05
$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, что сократит время выполнения.

 0
Author: Yellowledbet, 2013-04-12 13:48:11

Почему бы вам просто не создать слизняк и не оставить остальную часть работы, которая включает индексирование в 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, при дублировании: добавьте суффикс и повторно вставьте

 0
Author: Danijel, 2017-05-23 11:46:33

Вы могли бы использовать Fbeen/uniqueslugbundle. Этот пакет легкий и делает то, что ему нужно.

 0
Author: Frank B, 2015-09-23 14:36:08