Проектиране на линкова структура с .htaccess и mod_rewrite

Проектиране на линкова структура с .htaccess и mod_rewrite

В PHP, MySQL

За кого е предназначен този урок?

За хора, които вече знаят какво е php, mysql, apache, сървър, URL и т.н., или казано по-просто – този урок е за хора с опит в създаването на динамични сайтове, които искат да подобрят продукцията си. Тук няма за цел да обяснявам какво е .htaccess или mod_rewrite, затова пък обяснявам как се изгражда логически линковата структура на сайта.

Предупреждавам – тази статия е доста дълга. Който вече знае какво е линкова структура, вече я е проектирал, но не знае как да я осъществи чрез .htaccess и mod_rewrite, да скролира надолу.

Какво ще създадем?

Този път – нищо конкретно. Знам – шокиращо, нали? Ще разглеждаме само теория.

Да започваме…

Предпоставки за промяна на линковете в динамичните сайтове

Статичните сайтове ни позволяват да даваме различно име на всеки файл, съдържащ конкретно съдържание и по този начин да помагаме на търсачките да извличат информация за съдържанието дори и по името на файла, в който се съхранява.

Например, ако имаме три статии в един статичен сайт – „Защо нашата фирма е №1“, „Как нашите услуги ще Ви помогнат“ и „Изберете качеството“ можем спокойно да наименуваме файловете им съответно:

  • zashto-nashata-firma-e-n-1.html,;
  • kak-nashite-uslugi-shte-vi-pomognat.html;
  • izberete-kachestvoto.html.

Естествено, не разчитаме, че някой ще запомни точно изписването на тези дълги имена, но търсачките ще ги харесат, особено ако ключовите ни думи съвпадат с някои от думите в заглавието. При динамичните сайтове обаче това не е възможно, защото в един модул за новини, имаме един файл (примерно artcle.php) и той показва всички възможни статии, които имаме в базата данни. А ако трябва да създаваме 350+ отделни файла, всеки с различно име и всеки да вади собствения си материал от базата данни, то тогава какъв е смисъла от база изобщо?

По същата логика, статичните сайтове ни позволяват (на теория) да разположим всички файлове, касаещи услугите в една папка, примерно /services. Така търсачките ще виждат и думата услуга в линка и ще я асоциират и с този термин за търсене. Динамичните сайтове обикновено зареждат дизайна си от шаблон – набор функции, които генерират дизайна на страницата, съответно ако разделим динамичните ни страници в папки, според тяхната тематика, ни остава или да създаваме индивидуални библиотеки с функции, печатащи дизайна за всяка папка, или да слагаме абсолютните адреси до всеки елемент.

Не казвам, че е невъзможно, но е трудно, сложно за поддръжка и ненужно.

Открийте разликите

Ще разгледаме различни подходи към организацията на линковете и информацията в URL-ите. Нека вземем един домейн, да речем www.imenasajtani.com.

Нека предположим, че този сайт е статичен и има три секции – услуги, продукти и за нас. Ако се спрем на подхода с имената на файловете и папките, можем да го направим така:

  • www.imenasajtani.com/
  • www.imenasajtani.com/about.html
  • www.imenasajtani.com/services/
  • www.imenasajtani.com/products/

Така… всичко добре. Имаме един статичен about.html и две папки, в които са два index.html файлове, съдържащи препратки към услугите и продуктите. Което означава, че файловете за продуктите ни ще бъдат с линк

www.imenasajtani.com/products/ime-na-produkt.html

и са в папката продукти, за да може търсачките винаги да ги асоциират с термина „products“, макар това да не е задължително.

Но какво става ако сайта е динамичен? Ако е динамичен, най-лесно откъм програмиране е да създадем скрипт page.php или да въведем същата логика в index.php, който да приема аргументи през $_GET протокола за това какво да зареди. Eто така:

  • www.imenasajtani.com/
  • www.imenasajtani.com/index.php?p=about
  • www.imenasajtani.com/index.php?p=services
  • www.imenasajtani.com/index.php?p=products

Това решение, макар да е лесно за програмиста, може да предизвика много проблеми:

  1. Трудно е за запомняне – крайния потребител не може да помни всички параметри, които трябва да се предават на скрипта. А какво ще стане, ако добавим и променлива за езика? Нещо от сорта на www.imenasajtani.com/page.php?p=about&lang=bg. Истински Ад, нали? Обзалагам се, че никой няма да иска да помни линкове, включващи питанки, амперсанди и пр.;
  2. Позволява злонамереност$_GET променливите разкриват на потребители с опит в php какво може да се случва в скрипта, което не е добре. Ако някой реши да си прави гаргара с вашия сайт, може да въвежда информация, която вие не очаквате или която базата ви изобщо не може да интерпретира. Да не говорим и, че е напълно възможно да се строши базата ви данни стига някой да успее да налучка правилната й структура – всеки среден хакер може да накара базата да му разкаже и майчиното си мляко само през една необезопасена $_GET заявка;
  3. Трудно се чете от търсачките – някои паяци не се справят добре с кролването и индексирането на сайтове с параметри. Няма да е никак хубаво да вложите десетки човекочаса в програмирането на един сайт, само за да можете след това да чакате месеци, за да се индексира и пак да имате 100 индексирани от общо 600 линка.

Проектиране на линкова структура

Сайт със сложна структура на папки и файлове е трудна за поддръжка инициатива, изисква огромен ресурс и всяка промяна е трудна, затова подобно решение на проблема с линковете е да заставим сървъра си да ги показва по друг начин, за да може потребителите ни и търсачките да се справям по-добре с линковете ни.

За целта ще ни трябва комплексно решение, което да имплементира в себе си няколко едновременни подхода:

  1. Линково дърво – за нашия сайт трябва да изградим структура/дърво на линковете. То трябва да се измисли преди да се започне работа по проекта, за да можем да го приложим без да се налагат редакции в последствие;
  2. Координация на базата данни – за да създадем линковата ни структура, елементите от нашата база данни трябва да носят информация за своето положение в дървото;
  3. Хард-кодиране на линковата структура в сайта – след като сме изградили предните две, трябва да насложим изработената линкова структура директно в кода. Т.е. връзките в сайта трябва да сочат до заместителите на реалните документи и параметри.

Сега, ще продължим с примерния ни сайт www.imenasajtani.com. Той е динамичен, но ние ще накараме сървъра да „излъже“ търсачките и потребителите, че не е. Т.е. всичките ни линкове ще сочат към папки или към .html документи. Ще успеем, като му изградим линкова структура и я вложим в базата му данни. Да започваме.

Линково дърво

Линково дърво

Пърово, което трябва да направим е да изградим линковото дърво на сайта. То има един корен – индекса на сайта ни и оттам се разклонява. Нека вземем примера по-горе – За нас, Продукти, Услуги. Коренът на дървото ще бъде www.imenasajtani.com. От него ще тръгват три клона – index.php?p=about, index.php?p=products и index.php?p=services. От клоните на услуги и продукти ще тръгват клоните на… продуктите и услугите, които ще бъдат в документа article.php?t=[type]&s=[slug], където type е една от трите ни секции – about, products и services, а slug е презентацията на името на материала/страницата/съдържанието, които изискваме от сървъра.

По-горе обяснявахме, че статията „Защо нашата фирма е №1“ може да се помести във файл, който да се казва zashto-nasahata-firma-e-n-1.html. В този пример „zashto-nasahata-firma-e-n-1“ се явява slug на статията.

Отдясно представяме структурата, която искаме да бъде видима за потребителите ни, т.е. как да изглеждат заместващите ни линкове. Както виждаме от схемата, ако сме в секция За нас, макар да оставаме и да използваме файла index.php, ние искаме потребителите да виждат линка като www.imenasajtani.com/about/, т.е. виждат само type параметъра на заявката без да виждат името на променливата и файла, който тя адресира.

Тази техника освен, че прави линка по-ефективен (лесен за запомняне, изчистен от GET заявки и лесен за индексиране от търсачките), го прави и по-труден за разбиване от недоброжелатели, защото те виждат само стойността, без да виждат променливата, за която се отнася. Това е все едно да решат задачата:

„Ако отговорът е 4, какъв е въпроса?“

Нека продължим по дървото. Ако потребителят продължи да разглежда сайта ще стигне до www.imenasajtani.com/products/, където ще открие линкове към продуктите, които предлагаме. Те ще изглеждат така:

www.imenasajtani.com/products/ime-na-produkta.html,

но всъщност ще адресират файла article.php, предавайки му информацията за това, че потребителят идва от секция products и иска да разгледа статията със slug ime-na-produkta.

Координация на базата данни

Това общо взето означава, че трябва да приспособим базите данни, с които изграждаме сайта си да разбират заявките. Ще разгледаме структурата на таблиците и базите за този сайт без да използваме маскиране на линковете и след това ще добавим нужните ни колони, за да сравним какво точно ще променя в организацията на данните ни.

Нека създадем таблица в базата ни ‘imenasajtani’, която ще се казва ‘index’:

<?php
$sql = mysql_query("CREATE TABLE  `imenasajtani`.`index`
(
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`title` VARCHAR( 200 ) CHARACTER SET cp1251 COLLATE cp1251_bulgarian_ci NOT NULL ,
`content` TEXT CHARACTER SET cp1251 COLLATE cp1251_bulgarian_ci NOT NULL
) ;");
?>

В тази таблица ще съхраняваме трите си секции – За нас, Продукти и Услуги:Таблица index

Ако не прибавим slug за таблица индекс, ще се налага да пишем три заявки към базата във файла index.php, които да викат всеки запис, според стойността на $p. А какво ще стане, ако след време решим да добавим нова секция? Примерно… Контакти? Ще трябва да редактираме кода на index.php да обработва заявка за $p = ‘contacts’.

Затова ще трябва да променим базата index, така че тя самата да разбира стойността на $p. така с една заявка ще можем да вадим нужната ни секция:

<?php
$sql = mysql_query("ALTER TABLE  `index` ADD  `slug` VARCHAR( 200 ) NOT NULL AFTER  `id`");
?>

Готово – вече имаме колоната slug и можем да й зададем стойностите, с които ще работят и заестващите линкове:

Таблица index с добавен slug

Сега заявката, които ще прави index.php ще изглежда така:

<?php
$sql = mysql_query("SELECT * FROM `index` WHERE `slug` = '$p'");
?>

И ще сме свободни да създаваме колко искаме на брой секции без да редактираме съдържанието на скрипта, а само добавяйки нов запис в базата.

В реално приложение article.php, ще борави с различни таблици, за различните секции, но тук ще разработим сценарий, в който услугите и продуктите ще се представят само с текст, следователно ни е достатъчна и една таблица. Информацията, която трябва да съхраняваме в нея е id на материала, slug на категорията, към която принадлежи (about, services или products) и собствения му slug или нашата таблица articles ще изглежда така:

Таблица articles

Така, базата данни е координирана с линковото дърво, което създадохме преди малко. Вече базата ни е способна да разпознава и асоциира slug-овете на различните категории с конкретни материали, които им принадлежат.

Хард-кодиране на линковата структура в сайта

Фрагментът, който печата линк към секция ще точи информацията си от таблицата index и ще изглежда така:

$d = 'www.imenasajtani.com/';
$p .= '<a href="'.$d.'/'.$r['slug'].'/"> '.$r['title'].' </a>';

Фрагментът, който печата линк към материал ще точи информацията си от таблицата articles и ще изглежда така:

$d = 'www.imenasajtani.com/';
$p .= '<a href="'.$d.'/'.$r['catslug'].'/'.$r['slug'].'.html"> '.$r['title'].' </a>';

Това е. Сега, след като направихме всичко това е редно да разберете как точно става подмяната на линковете от www.imenasajtani.com/index.php?p=about на www.imenasajtani.com/about/.

.htaccess и mod_rewrite

mod_rewrite е модул от сървърната среда на Apache, който осъществява подмяна на линковете към файлове и директории, уповавайки се на правила, дефинирани в конфигурационния файл на нашата root директория, т.е. в .htaccess файла.

mod_rewrite борави с правила, които се наричат Rewrite rules и всяко от тях определя преработката на определен линк. Когато почнем да пишем .htaccess файла за нашия сайт, трябва да направим няколко неща:

  1. Включване на двигателя за правила за презапис – много е важно, иначе до никъде няма да стигнем;
  2. Канонически запис на сайта ни – това е варианта, в който искаме да се появява нашия сайт на потребителя – с www.  или без него. Каноническия запис ще спести на Google и компания чуденето дали www.imenasajtani.com и http://imenasajtani.com са едно и също нещо и дали в линковете на двата варианта има разлика;
  3. Съставяне на неконфринтиращи се правила за презапис – иначе някои от линковете ни няма да работят, както ни се иска;

В самото начало на файла трябва да уведомим сървъра, че възнамеряваме да правим корекции в линковата структура. Затова тряябва да посоччим, че ще включваме Двигателя за правилата за презапис:

Options +FollowSymlinks
RewriteEngine On
RewriteBase /

Options +FollowSymlinks е ред, който всъщност каза на сървъра да следва т.нар. символни линкове. Чрез него сървърът знае, че е редно да следва и нереалните връзки, дефиниращи файловете в локации, които физически може да не съществуват.

RewriteEngine On е командата, с която стартираме двигателя, а RewriteBase / е команда, с която .htaccess казва на сървъра, че всички правила, описани във файла важат само за тази директория. Това е важно, в случай, че инсталираме сайт в subdomain, защото макар за потребителя да изглежда като отделен сайт, в сървърната среда, субдомейните са просто папки.

Окей, .htaccess е настроен за работа. Сега нека дефинираме каноническия запис на сайта:

RewriteCond %{HTTP_HOST} ^imenasajtani\.com
RewriteRule ^(.*)$ http://www.imenasajtani.com/$1 [R=301,NC]

Първия ред тук е Условие за Презаписване, т.е. при какви условия трябва да използваме даденото Правило. В случая избираме за канонически запис www.imenasajtani.com. Така дори и в адресната лента да се напише imenasajtani.com, след зареждане, там ще се появят www., защото сървърът автоматично ще препраща всички заявки за сайта на адрес www.imenasajtani.com, чрез протокол 301, който означава „перманентно преместен“. NC е съкращение за no case и обозначава, че няма значение с какви букви е изписан адреса – малки или големи.

И вече идва реда на съставянето на правилата за нашия сайт. Те са две на брой – едното касае index.php, а другото – articles.php.

Правилото за index.php трябва да казва на сървъра да изреже ?p= от заявката. Всеки ред на RewriteRule е разделен на две части. Първата е шаблона на линка, който ще бъде подаден на сървъра и втората – шаблона на линка, който реално трябва да бъде зареден в прозореца на браузъра, така че записа на правилото ни за index.php ще изглежда така:

RewriteRule ^/([^/\.]+)/?$       /index.php?p=$1

^/([^/\.]+) е регулярен израз, който казва на сървъра, че след www.imetonasajtani.com/ очаква един или повече символа, които не са точка или слеш (ето това е слеш – / или наклонена черта). Всичко, което отговаря на това условие ще бъде хванато от сървъра и ще бъде присвоено на променливата $1, която автоматично ще бъде подадена като стойност на $p в скрипта на index.php.

/?$ се грижи да се увери, че след наклонената черта няма нищо друго. Ако сървърът намери, че има, това правило ще бъде игнорирано.

Сега нека направим правилото за articles.php. При него имаме две променливи, които в обикновен запис изглеждат така: ?t=[type value]&s=[slug value], т.е. $t ще получи стойността на $1, а s – стойността на $2.

RewriteRule ^/([^/\.]+)/([^/\.]+)/?$  /articles.php?t=$1&s=$2    [L]

Това правило казва, че след името на сайта имаме една наклонена черта /, след него имаме някакъв низ от един или повече символа. След това още една наклонена черта и пак низ. Всеки низ ще се вземе и ще се присвои, съответно на $1 и $2.

Флагът [L] показва на сървъра, че ако това правило се изпълни успешно, не е необходимо да сравнява да проверява следващите.

С това проектирането и изпълнението на линковата структура на сайта приключва. Сега потребителите ще ползват заместващите линкове, а сървъра ще работи с физическите файлове. Надявам се урока ви е бил полезен. Успех 🙂

За Гергин Борисов

+359 877080887

Здравейте, аз съм Гергин Борисов, на 24 години от София и се занимавам с уеб и лого дизайн, предпечат и отскоро - с блога си. Занимавам се с дизайн от 2006та година насам, като преди това (от 2004та) работех в сферата на предпечата с баща ми, фотографът Александър Борисов.