Постраничная навигация на PHP

в 20:20, , рубрики: php, php скрипт, постраничная навигация

Часто при разработке и выводе контента появляется необходимость использования постраничной навигации. Кто-то скорее всего использует готовые решения от своего фреймворка. Кто-то, возможно, не заморачивается и лупит страницы просто циклом. У кого-то есть свои наработки в этом направлении. Вот я как раз и хочу поделиться своим решением данной задачи.

Существует множество вариаций расположения и отображения кнопок, лично я пришел к следующему решению, которое по моему мнению наиболее наглядно и удобно. Подходит как для 5 страниц так и для 5000.

Пример HTML кода

Не буду ходить вокруг да около, сразу приложу пример сформированного скриптом html кода:

<div class="navigation">
	<a href="/playlist/1.html?page=6">Назад</a>
	<a href="/playlist/1.html">1</a>
	<i>...</i>
	<a href="/playlist/1.html?page=4">4</a>
	<a href="/playlist/1.html?page=5">5</a>
	<a href="/playlist/1.html?page=6">6</a>
	<span class="link_active">7</span>
	<a href="/playlist/1.html?page=8">8</a>
	<a href="/playlist/1.html?page=9">9</a>
	<a href="/playlist/1.html?page=10">10</a>
	<i>...</i>
	<a href="/playlist/1.html?page=17">17</a>
	<a href="/playlist/1.html?page=8">Вперед</a>
</div>

Логика построения

По настройкам параметров я остановлюсь чуть позже после приведения кода, сейчас опишу логику формирования самих номеров.

С кнопками «Назад» и «Вперед» думаю все понятно, к тому же их можно просто отключить, поэтому на них не буду заострять внимания.

Первый и последний номер страницы отображается всегда, своего рода кнопки «В начало» и «В конец».

Середина формируется уже по простому алгоритму. Отображается просматриваемая страница и по N страниц по бокам. На примере отображается по N=3 страницы. В принципе все просто и понятно, но особая хитрость используется при приближении к краям. Опишу на примерах:

Страница 1-3 (где 3 = N)

1 2 3 4 5 6… 17

Отображаются первые N*2 страниц и последняя.

Страница 4

1 2 3 4 5 6 7… 17

Отображается первая и дальше сформированная строка от 4-3=1 до 4+3=7. Первая страница зарезервирована поэтому формируются номера от 2 до 7.

Страница 5

1 2 3 4 5 6 7 8… 17

от 5-3=2 до 5+3=8.

Страница 6

1 2 3 4 5 6 7 8 9… 17

Пожалуй во всех навигациях что я видел (включая хабр) строка была бы сформирована с пропуском, т.е. 1… 3 4 5 6 7 8 9… 17
Но ведь это не логично, отображать многоточие вместо одного числа. При построении второго многоточия выполняется аналогичная проверка.

Страница 7

1… 4 5 6 7 8 9 10… 17

Середина уже стандартна.

Формирование окончания аналогично началу

Страница 12

1… 9 10 11 12 13 14 15 16 17

Страница 13

1… 10 11 12 13 14 15 16 17

Страница 14

1… 11 12 13 14 15 16 17

Страница 15-17

1… 12 13 14 15 16 17

Редиректы

Помимо этого из особенностей хочу выделить еще 2 момента, это проверка существования страницы и редирект на «правильный» адрес. Т.е. к примеру, тут же на хабре первая страница может быть доступна сразу по 2м адресам:
habrahabr.ru/sandbox/page1
habrahabr.ru/sandbox

Скрипт не дает зайти на адрес page/1/ и выполняет редирект на «чистый» адрес
Так же если указан слишком большой номер страницы будет выполнен редирект на последнюю существующую. К примеру были удалены материалы или изменено количество записей на страницу. Не могу правда однозначно сказать полезно ли это будет с точки зрения СЕО, но для пользователей мне кажется так будет удобнее.

PHP код и его использование

PHP код

class navigation
{
	/**
	 * @var string чистый URL по умолчанию
	 * В адресе может быть указано место для размещения блока с номером страницы, тег {page}
	 * Пример:
	 * /some_url{page}.html
	 * В итоге адрес будет:
	 * /some_url.html
	 * /some_url/page_2.html
	 * Если тег {page} не указан, то страницы будут дописываться в конец адреса
	 */
	var $base_url = '/';

	/**
	 * @var string шаблон ссылки навигации
	 */
	var $tpl = 'page/{page}/';

	/**
	 * @var string Обертка кнопок
	 */
	var $wrap = "n<div class="navigation">n{pages}</div>nn";

	/**
	 * @var int сколько показывать кнопок страниц до и после актуальной
	 * Пример:
	 * $spread = 2
	 * Всего 9 страниц навигации и сейчас просматривают 5ю
	 * 1 ... 3 4 5 6 7 ... 9
	 */
	var $spread = 5;

	/**
	 * @var string разрыв между номерами страниц
	 */
	var $separator = "t<i>...</i>n";

	/**
	 * @var string имя класса активной страницы
	 */
	var $active_class = 'link_active';

	/**
	 * @var int номер просматриваемой страницы
	 */
	var $current_page = 0;

	/**
	 * @var bool показывать кнопки "Вперед" и "Назад"
	 */
	var $next_prev = true;

	/**
	 * @var string текст кнопки "Назад"
	 */
	var $prev_title = 'Назад';

	/**
	 * @var string текст кнопки "Вперед"
	 */
	var $next_title = 'Вперед';

	/**
	 * Инициализация класса
	 * @param string $base_url URL в конец которого будет добавляться навигация
	 */
	
	public function __construct( $base_url = '/' )
	{
		$this->base_url = $base_url;
	}

	/**
	 * Строим навигации и формируем шаблон
	 * @param int $limit количество записей на 1 страницу
	 * @param int $count_all общее количество всех записей
	 * @param int $current_page номер просматриваемой страницы
	 * @return - Сформированный шаблон навигации готовый к выводу
	 */
	public function build( $limit, $count_all, $current_page = 1 )
	{
		if( $limit < 1 OR $count_all <= $limit ) return;
		$count_pages = ceil( $count_all / $limit );
		if( $current_page > $count_pages ) {
			header( "HTTP/1.0 301 Moved Permanently" );
			header( "Location: " . $this->get_url( $count_pages ) );
			die( "Redirect" );
		}
		if( $current_page == 1 AND $_SERVER['REQUEST_URI'] != $this->get_url( $current_page ) )
		{
			header( "HTTP/1.0 301 Moved Permanently" );
			header( "Location: " . $this->get_url( $current_page ) );
			die( "Redirect" );
		}
		
		$this->current_page = intval( $current_page );
		if( $this->current_page < 1 ) $this->current_page = 1;

		$shift_start = max( $this->current_page - $this->spread, 2 );
		$shift_end = min( $this->current_page + $this->spread, $count_pages-1 );
		if( $shift_end < $this->spread*2 ) $shift_end = min( $this->spread*2, $count_pages-1 );
		if( $shift_end == $count_pages - 1 AND $shift_start > 3 ) $shift_start = max( 3, min( $count_pages - $this->spread*2 + 1, $shift_start ) );

		$list = $this->get_item( 1 );
		
		if( $shift_start == 3 ) $list .= $this->get_item( 2 );
		elseif( $shift_start > 3 ) $list .= $this->separator;
		
		for( $i = $shift_start; $i <= $shift_end; $i++ ) $list .= $this->get_item( $i );
		
		$last_page = $count_pages - 1;
		if( $shift_end == $last_page-1 ) $list .= $this->get_item( $last_page );
		elseif( $shift_end < $last_page ) $list .= $this->separator;

		$list .= $this->get_item( $count_pages );
		
		if( $this->next_prev ) $list = $this->get_item( $this->current_page > 1 ? $this->current_page - 1 : 1, $this->prev_title, true ) . $list . $this->get_item( $this->current_page < $count_pages ? $this->current_page + 1 : $count_pages, $this->next_title, true );

		return str_replace( "{pages}", $list, $this->wrap );
	}
	
	/**
	 * Формирование адреса
	 * @param int $page_num номер страницы
	 * @return string сформированный адрес
	 */
	private function get_url( $page_num = 0 )
	{
		$page = $page_num > 1 ? str_replace( '{page}', $page_num, $this->tpl ) : '';
		if( stripos( $this->base_url, '{page}' ) !== false ) return str_replace( '{page}', $page, $this->base_url );
		else return $this->base_url . $page;
	}

	/**
	 * Формирование кнопки/ссылки
	 * @param int $page_num номер страницы
	 * @param string $page_name если указано, будет выводиться текст вместо номера страницы
	 * @param bool $noclass 
	 * @return - span блок с активной страницей или ссылку.
	 */
	private function get_item( $page_num, $page_name = '', $noclass = false )
	{
		$page_name = $page_name ?: $page_num;
		$class = $noclass ? '' : ' class="' . $this->active_class . '"';
		if( $this->current_page == $page_num ) return "t<span" . $class . ">" . $page_name . "</span>n";

		return "t<a href="" . $this->get_url( $page_num ) . "">" . $page_name . "</a>n";
	}
}

Для наглядности, приведу пример построения навигации песочницы:
habrahabr.ru/sandbox/page12

$navi = new navigation( "/sandbox/" );
$navi->tpl = "page{page}/";
$navi->spread = 4;
$template = $navi->build( $limit, $count_all, $page_num );

Или же если номер страницы прописан внутри URL:
example.com/some_url/1.html — первая страница
example.com/some_url/1-page2.html — вторая страница

$navi = new navigation( "/some_url/1{page}.html" );
$navi->tpl = "-page{page}";
$template = $navi->build( $limit, $count_all, $page_num );

где

$limit — количество записей на страницу
$count_all — общее количество записей
$page_num — номер страницы на которой находится пользователь

На этом, пожалуй, всё. Буду рад любой конструктивной критике.

Автор: Sandev

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js