Как стать автором
Обновить

Как сделать FULLTEXT поиск в CMS на CodeIgniter

Время на прочтение10 мин
Количество просмотров6.2K
На Хабре в последнее время появляется все больше постов посвященных набирающему популярность framework'у CodeIgniter. Это довольно простой и удобный фреймворк с помощью которого можно быстро начать делать нормальные приложения на PHP. Под катом пример реализации FULLTEXT поиска в CMS на CodeIgniter.

1. Подготовка CodeIgniter.

Скачиваем и распаковываем дистрибутив CodeIgniter в какую-нибудь папку на вашем сервере. Создаем базу данных, пользователя с правами доступа к этой БД и прописываем настройки подключения в application/config/database.php.
Открываем application/config/config.php и выставляем настройку поля base_url.

2. Настройка Базы данных.

БД мы уже создали в первом шаге, но у нас нет таблиц. Создадим простую таблицу pages.
CREATE TABLE pages (
 id int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
 url text NOT NULL,
 title text NOT NULL,
 content text NOT NULL,
 updated datetime NOT NULL,
 PRIMARY KEY (id),
 FULLTEXT KEY content (content)
) ENGINE=MyISAM


* This source code was highlighted with Source Code Highlighter.

При использовании FULLTEXT необходимо указать все поля по которым мы будем производить поиск. В данной таблице поиск будет производиться только по полю content. Обратите внимание, что FULLTEXT поиск работает только с использованием MyISAM engine.

3. Тестовые данные

Для тестирования нашего поискового механизма в таблицу необходимо добавить некоторое количество тестовых данных. Я решил что Википедия может стать хорошим донором для нашей БД. Я написал небольшой скрипт, который берет несколько последних статей и потом с помощью экспорта Википедии заносит эти статьи в БД.
4. Поиск по базе
Для поиска мы будем использовать SQL запрос вида:
SELECT *
FROM pages
WHERE MATCH (content) AGAINST ('test') > 0


* This source code was highlighted with Source Code Highlighter.

При использовании FULLTEXT поиска необходимо помнить о нескольких ограничениях.
  • Длина запроса должна превышать 3 символа, иначе поиск вернет пустой результат.
  • Существует набор стоп слов который MySQL игнорирует при поиске (‘the’, ‘however’, ‘hello’). Если вы попытаетесь искать по этим словам, результат поиска будет пустым.
  • Также доступен расширенный поиск. Полную информацию можно найти в документации к MySQL.


5. CodeIgniter и MVC паттерн.

Вернемся к нашему фреймворку. CodeIgniter предоставляет популярный паттерн проектирования модель-вид-контроллер (MVC model-view-controller).
Напомню основные правила:
  • Все изменения и работа с БД производиться из модели
  • Ничего не может быть выведено пользователю из контроллера или модели
  • Только одно выражение может быть внутри тегов сокращенную запись PHP)

Если вы нарушаете какое-либо из этих правил, то стоит вернуться назад и еще раз подумать над структурой вашего проекта.
Начнем разработку поиска с файла модели. Я использую только один метод, который выполняет поиск. Вы можете заметить, что здесь использован «прямой SQL запрос», хотя в CodeIgniter есть ActiveRecord позволяющий упростить формирование запросов.
Ниже представлен код модели располагающейся в application/models/page_model.php
class Page_model extends Model
{
  function Page_model()
  {
    parent::Model();
    // Делаем доступной БД во всех функциях
    $this->load->database();
  }
  function search($terms)
  {
    // Выполнение запроса и возврат результата
    $sql = "SELECT url, title
          FROM pages
          WHERE MATCH (content) AGAINST (?) > 0"
;
    $query = $this->db->query($sql, array($terms, $terms));
    return $query->result();
  }
}


* This source code was highlighted with Source Code Highlighter.

Следующим шагом создадим файл вида:
<?php $this->load->helper('form'); ?>
<?php echo form_open($this->uri->uri_string); ?>
<?php echo form_label('Search:', 'search-box'); ?>
<?php echo form_input(array('name' => 'q', 'id' => 'search-box', 'value' => $search_terms)); ?>
<?php echo form_submit('search', 'Search'); ?>
<?php echo form_close(); ?>
<?php if ( ! is_null($results)): ?>
  <?php if (count($results)): ?>
    <ul>
    <?php foreach ($results as $result): ?>
      <li><a href="&#60;?php echo $result->url; ?>"><?php echo $result->title; ?></a></li>
    <?php endforeach ?>
    </ul>
  <?php else: ?>
    <p><em>There are no results for your query.</em></p>
  <?php endif ?>
<?php endif ?>


* This source code was highlighted with Source Code Highlighter.

Теперь пришла очередь создать файл контроллера, который свяжет модель и вид:
class Pages extends Controller {
  function search($search_terms = '')
  {
    // Если форма отправлена перепишем URL добавив строку запроса
    // обратите внимание, что с некоторыми символами
    // могут быть проблемы.
    if ($this->input->post('q'))
    {
      redirect('/pages/search/' . $this->input->post('q'));
    }
    if ($search_terms)
    {
      // Загрузка модели и выполнение поиска по БД
      $this->load->model('page_model');
      $results = $this->page_model->search($search_terms);
    }
    // Загрузка файла вида и вывод на экран
    $this->load->view('search_results', array(
      'search_terms' => $search_terms,
      'results' => @$results
    ));
  }
}


* This source code was highlighted with Source Code Highlighter.

В начале этого метода мы использовали функции redirect. Это функция содержится в хелпере URL. Для использовании этой функции мы производили «ручную» загрузку хелпера с помощью $this->load->helper(‘url’); Т.к. мы будем часто использовать этот хелпер, то проще сразу прописать его в автозагрузку, чтобы не загружать все время его вручную. Для этого редактируем файл application/config/autoload.php. Дописываем в раздел $autoload['helper']:
$autoload['helper'] = array('url');

* This source code was highlighted with Source Code Highlighter.

Теперь мы получили рабочий прототип. Не забудьте, что в вашей БД должны быть данные (не меньше 3 строк). Теперь наберите в вашем браузере что-то типа ‘http://localhost/index.php/pages/search’, введите в строку поиска какой-нибудь текст содержащийся в БД и нажмите поиск. Результат будет выглядеть примерно так:
image

6. Добавление новых возможностей в наш поиск.

Для начала добавим постраничную навигацию для результатов поиска. Для вывода постраничной навигации в CodeIgniter используется класс Pagination.
Модифицируем нашу модель для вывода постраничной навигации. Для этого нам необходимо задать количество записей выбираемых из БД, номер записи с которой производить выборку, а также получить общее количество записей по данному запросу.
Вот что у нас получилось:
class Page_model extends Model {
  function search($terms, $start = 0, $results_per_page = 0)
  {
    // Зададим лимит выбираемых записей
    //и стартовую позицию
    if ($results_per_page > 0)
    {
      $limit = "LIMIT $start, $results_per_page";
    }
    else
    {
      $limit = '';
    }
    // Выполнение SQL запроса
    $sql = "SELECT url, title, content
          FROM pages
          WHERE MATCH (content) AGAINST (?) > 0
          $limit"
;
    $query = $this->db->query($sql, array($terms, $terms));
    return $query->result();
  }
  function count_search_results($terms)
  {
    // Run SQL to count the total number of search results
    $sql = "SELECT COUNT(*) AS count
          FROM pages
          WHERE MATCH (content) AGAINST (?)"
;
    $query = $this->db->query($sql, array($terms));
    return $query->row()->count;
  }
}


* This source code was highlighted with Source Code Highlighter.

Следующим шагом модифицируем наш контроллер для работы с постраничной навигацией
class Pages extends Controller {
  function search($search_terms = '', $start = 0)
  {
    // Если форма отправлена перепишем URL добавив строку запроса
    // обратите внимание, что с некоторыми символами
    // могут быть проблемы.
    if ($this->input->post('q'))
    {
      redirect('/pages/search/' . $this->input->post('q'));
    }
    if ($search_terms)
    {
      // Определим сколько результатов
      //выводить на страницу
      $results_per_page = $this->config->item('results_per_page');
      // Загружаем модель, выполняем поиск, определяем
      // сколько всего результатов поиска
      $this->load->model('page_model');
      $results = $this->page_model->search($search_terms, $start, $results_per_page);
      $total_results = $this->page_model->count_search_results($search_terms);
      // Загрузка постраничной навигации
      $this->_setup_pagination('/pages/search/' . $search_terms . '/', $total_results, $results_per_page);
      // Определяем какие результаты выводить
      $first_result = $start + 1;
      $last_result = min($start + $results_per_page, $total_results);
    }
    // Загрузка вида и вывод результатов
    $this->load->view('search_results', array(
      'search_terms' => $search_terms,
      'first_result' => @$first_result,
      'last_result' => @$last_result,
      'total_results' => @$total_results,
      'results' => @$results
    ));
  }
  function _setup_pagination($url, $total_results, $results_per_page)
  {
    // Не забываем загрузить постраничную навигацию
    $this->load->library('pagination');
    $uri_segment = count(explode('/', $url));
    // Инициализация постраничной навигации и установка
    // необходимых параметров
    $this->pagination->initialize(array(
      'base_url' => site_url($url),
      'uri_segment' => $uri_segment,
      'total_rows' => $total_results,
      'per_page' => $results_per_page
    ));
  }
}


* This source code was highlighted with Source Code Highlighter.

Давайте рассмотрим что у нас получилось. Мы добавили постраничную навигацию, а значит в URL у нас добавились новые сегменты. Этот сегмент является одновременно страницей результатов поиска и номером для определения стартовой позиции для выборки из БД.
Для задания количества результатов на странице поиска я использовал config library. Лучше всего добавить новый файл в папку application/config. Создадим файл application/config/application.php содержащий строчку:
$config['results_per_page'] = 10;

* This source code was highlighted with Source Code Highlighter.

Добавим его в автозагрузку изменив опять файл application/config/autoload.php и добавим ‘application’ в $autoload['config'] секцию.
Также мы используем методы для обработки выводимой в результатах поиска информации (сниппет и подстветка в сниппете слов из запроса). Эти методы содержаться в нашем хелпере в файле application/helpers/search_helper.php. Первая функция search_highlight($text, $search_terms) выделяет слова из запроса в результатах поиска, вторая search_extract($content, $search_terms, $number_of_snippets = 3, $snippet_length = 60) извлекает из контента часть содержащую заданный в поиске запрос.
Так будет выглядеть наш доработанный файл вида:
<?php $this->load->helper(array('form', 'search')); ?>
<?php echo form_open($this->uri->uri_string); ?>
<?php echo form_label('Search:', 'search-box'); ?>
<?php echo form_input(array('name' => 'q', 'id' => 'search-box', 'value' => $search_terms)); ?>
<?php echo form_submit('search', 'Search'); ?>
<?php echo form_close(); ?>
<?php if ( ! is_null($results)): ?>
  <?php if (count($results)): ?>
    <p>Showing search results for '<?php echo $search_terms; ?>' (<?php echo $first_result; ?><?php echo $last_result; ?> of <?php echo $total_results; ?>):</p>
    <ul>
    <?php foreach ($results as $result): ?>
      <li><a href="&#60;?php echo $result->url; ?>"><?php echo search_highlight($result->title, $search_terms); ?></a><br /><?php echo search_extract($result->content, $search_terms); ?></li>
    <?php endforeach ?>
    </ul>
    <?php echo $this->pagination->create_links(); ?>
  <?php else: ?>
    <p><em>There are no results for your query.</em></p>
  <?php endif ?>
<?php endif ?>


* This source code was highlighted with Source Code Highlighter.

А так новые результаты поиска:
image

7. Еще больше вкусных фишек.

CodeIgniter предоставляет возможность вывода тестовых данных и различной дополнительной информации.
К пример так можно запустить работу теста для модели:
// Mark the start of search
$this->benchmark->mark('search_start');
// Load the model, perform the search and establish the total
// number of results
$this->load->model('page_model');
$results = $this->page_model->search($search_terms, $start, $results_per_page);
$total_results = $this->page_model->count_search_results($search_terms);
// Mark the end of search
$this->benchmark->mark('search_end');


* This source code was highlighted with Source Code Highlighter.

В итоге форма с результатами теста будет выглядеть так:
image
Теги:
Хабы:
Всего голосов 42: ↑32 и ↓10+22
Комментарии44

Публикации

Истории

Ближайшие события

One day offer от ВСК
Дата16 – 17 мая
Время09:00 – 18:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн
Антиконференция X5 Future Night
Дата30 мая
Время11:00 – 23:00
Место
Онлайн
Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область