Yii PHP framework: создаём игровой сайт. Часть 11. Человекопонятные URL.

Владимир | | PHP, Web разработка, Yii.

yii game site permalinks

В предыдущих частях мы практически доделали наш игровой сайт. Он в рабочем состоянии, но некоторые вещи выглядят не лучшим образом, и, прежде всего, это касается вида URL страниц.

По-умолчанию, Yii фреймоворк использует следующую схему для формирования адреса страниц:

http://имя_сайта.домен/index.php?r=контроллер/действие&параметр1=значение1...

Т.е. адрес формируется из GET параметров. В них передаётся вся информация, необходимая фреймворку для определения того, какую именно страницу нужно сформировать.

Все отлично, но в таком виде адрес очень плохо читается. Желательно было бы его сократить и использовать запись без GET параметров. Фреймворк позволяет нам сделать это, не изменяя код приложения. Нужно только указать соответствующие правила в файле конфигурации приложения (protected/config/main.php).

Вообще, я очень советую вам почитать официальный тьюториал Красивые адреса URL, а здесь мы рассмотрим, что можно сделать для нашего сайта.

На данный момент ссылки стандартные и имеют вид.

Страница с игрой
index.php?r=games/show&id=367

Игры выбранного жанра
index.php?r=games/list&type_id=1

Страницы игр выбранного жанра
index.php?r=games/list&type_id=4&page=2

Страницы общего списка игр
index.php?r=games/list&page=2

Архивы
index.php?r=games/archive&year=2010&month=1

Список скриншотов
index.php?r=screenshots/list

Выбранный скриншот
index.php?r=screenshots/show&id=2274

За работу с URL отвечает компонент urlManager. Попробуем просто его подключить и посмотрим, что изменится. Для этого добавляем в конфиг (protected/config/main.php) элемент urlManager в массив components.

'components'=>array(
…
	'urlManager'=>array(
		'urlFormat'=>'path',
…
	),
),

Обновляем страницу сайта и смотрим, как изменились url.

index.php/games/list
index.php/games/show/id/371
index.php/games/list/page/2
index.php/games/list/type_id/1
index.php/games/archive/year/2010/month/1
index.php/screenshots/show/id/2565
index.php/screenshots/list
index.php/screenshots/list/page/2

Учтите, что изменение произошло потому, что мы не прописывали «жёстко» ни одного url. Везде для формирования ссылок мы пользовались встроенными библиотеками фреймворка.

В принципе, этот вариант уже значительно лучше, но некоторые адреса содержат избыточную информацию.

Например, для показа страницы с игрой нам не нужен сегмент id в адресе, т.е. можно использовать вместо
index.php/games/show/id/371
более короткий адрес
index.php/games/show/371

Для этого создадим массив с правилами преобразования адресов.

'urlManager'=>array(
	'urlFormat'=>'path',
	'rules'=>array(
		'games/show/'=>'games/show',
…
	),
),

В массиве rules каждое правило записывается в отдельном элементе. Взгляните на картинку.

routing

Значение элемента должно содержать имена контроллера и действия (action), которые должны быть вызваны. Ключ элемента содержит начальные сегменты адреса (они не обязательно должны совпадать с названием контроллера и действия). После них в угловых скобках идет перечень параметров.

Параметры задаются в виде пары: имя параметра, шаблон, и разделяются двоеточием. При этом шаблон является обычным регулярным выражением. В данном случае мы \d+ означает – одна или более цифр.

Рассмотрим чуть более сложный пример. Уберём сегменты month и year из url архива.

'games/archive//'=>'games/archive'

Как видите, принцип точно такой же, только в этом случае в указываем два параметра. В результате url архивов будут выглядеть так.
index.php/games/archive/2010/1

Следующий шаг – убираем из адреса index.php.

Сделать это только средствами одного фреймворка без поддержки вебсервера невозможно. Если используется Apache, необходимо чтобы был подключен модуль mod_rewrite.

Создаём в корне сайта файл .htaccess со следующим содержимым.

Options +FollowSymLinks
IndexIgnore */*
RewriteEngine on

# если директория или файл существуют, используем их
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

# в противном случае перенаправляем запрос через index.php
RewriteRule . index.php

Тут нет никакого смысла изобретать велосипед. Эти правила обычные, я их взял из официальной документации.

Теперь нужно рассказать фреймворку, что адреса страниц нужно создавать без index.php.

Для этого используется параметр 'showScriptName'=>false.

Окончательно конфигурационный файл выглядит так.

'urlManager'=>array(
	'urlFormat'=>'path',
	'rules'=>array(
		'games/show/'=>'games/show',
		'screenshots/show/'=>'screenshots/show',
		'games/archive//'=>'games/archive',
	),
	'showScriptName'=>false,
),

Естественно, это не все возможности компонента urlManager, но для нашего сайта их достаточно. Подробнее их описание вы найдёте в соответствующем разделе документации.

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

Все разделы цикла.

  1. Yii PHP framework: создаём игровой сайт. Часть 1. Постановка задачи.
  2. Yii PHP framework: создаём игровой сайт. Часть 2. База данных и установка фреймворка.
  3. Yii PHP framework: создаём игровой сайт. Часть 3. Аутентификация.
  4. Yii PHP framework: создаём игровой сайт. Часть 4. Работа с жанрами игр.
  5. Yii PHP framework: создаём игровой сайт. Часть 5. Импорт игр.
  6. Yii PHP framework: создаём игровой сайт. Часть 6. Формируем страницы игр и жанров.
  7. Yii PHP framework: создаём игровой сайт. Часть 7. Работа с JavaScript и страницы игр.
  8. Yii PHP framework: создаём игровой сайт. Часть 8. Создаём виджеты.
  9. Yii PHP framework: создаём игровой сайт. Часть 9. Поиск ошибок.
  10. Yii PHP framework: создаём игровой сайт. Часть 10. Панель управления.
  11. Yii PHP framework: создаём игровой сайт. Часть 11. Человекопонятные URL.
  12. Архив с исходниками

Интересно почитать

Для всех вебмастеров, которым приходится работать с Linux серверами, пригодится Конспект лекций по курсу «Администратор ПК с Linux».

  • Sam

    Отличная серия вышла. Пожалуй, самый масштабный неофициальный туториал.

    Какие планы на Yii?
    Понравился ли фреймворк?
    Что понравилось и не понравилось?
    Будут ли ещё посты?

  • Sam

    Отличная серия вышла. Пожалуй, самый масштабный неофициальный туториал.

    Какие планы на Yii?
    Понравился ли фреймворк?
    Что понравилось и не понравилось?
    Будут ли ещё посты?

  • alexy4b

    Благодарю за tutorial. Привет Sam (: Поддерживаю вопросы Sam'а!

  • alexy4b

    Благодарю за tutorial. Привет Sam (: Поддерживаю вопросы Sam'а!

  • Большое спасибо!

    Yii мне понравился и точно будут им пользоваться, может быть не для всех проектов.

    Понравилось большое количество встроенных возможностей, которые очень часто используются на реальных сайтах (поддержка js — яркий пример). Этого очень не хватает в CI. Вообще было ощущение, что авторы перед разработкой фреймворка составили перечень наиболее востребованных функций и делали фреймворк под них.

    Не понравилось. Документация по части возможностей есть только в виде Class Reference. Была ещё заморочка с базой данных. При переходе на версию 1.1 изменили принцип формирования запросов. Кстати, я до конца не разобрался с этим моментом.

    Посты точно будут 🙂 Планирую рассказать о кешировании и поддержке тем. Может быть сделаю их в виде продолжения к этому циклу.

  • Большое спасибо!

    Yii мне понравился и точно будут им пользоваться, может быть не для всех проектов.

    Понравилось большое количество встроенных возможностей, которые очень часто используются на реальных сайтах (поддержка js — яркий пример). Этого очень не хватает в CI. Вообще было ощущение, что авторы перед разработкой фреймворка составили перечень наиболее востребованных функций и делали фреймворк под них.

    Не понравилось. Документация по части возможностей есть только в виде Class Reference. Была ещё заморочка с базой данных. При переходе на версию 1.1 изменили принцип формирования запросов. Кстати, я до конца не разобрался с этим моментом.

    Посты точно будут 🙂 Планирую рассказать о кешировании и поддержке тем. Может быть сделаю их в виде продолжения к этому циклу.

  • lo

    попробуем

  • lo

    попробуем

  • alexy4b

    Хорошие впечатления об Yii. Не забудь затрагивать темы асинхронного обмена данными. Ещё раз благодарю за проделанный труд! Успеха тебе!

    • Спасибо!
      Совет постараюсь учесть 😉

  • alexy4b

    Хорошие впечатления об Yii. Не забудь затрагивать темы асинхронного обмена данными. Ещё раз благодарю за проделанный труд! Успеха тебе!

    • Спасибо!
      Совет постараюсь учесть 😉

  • Денис

    Спасибо, Владимир. Твои статьи очень помогли ). Все работает, только при клике на любой жанр выдает такую бяку (использую WAMP):

    CDbException

    Описание

    CDbCommand не удалось исполнить SQL-запрос: SQLSTATE[42S22]: Column not found: 1054 Unknown column 't_id' in 'where clause'
    Исходный код

    D:wampwwwtoolsyiiframeworkdbCDbCommand.php(375)
    ——————————————————-

    Кстати ). Советую на страницу первого поста поместить информацию, что в файле конфигурации Apache надо сделать изменения для правильных url.

    • Вы используете версию 1.1 фреймворка? В ней есть изменения в работе с базой.
      Этот пример я тестировал на 1.0.10.

  • Денис

    Спасибо, Владимир. Твои статьи очень помогли ). Все работает, только при клике на любой жанр выдает такую бяку (использую WAMP):

    CDbException

    Описание

    CDbCommand не удалось исполнить SQL-запрос: SQLSTATE[42S22]: Column not found: 1054 Unknown column 't_id' in 'where clause'
    Исходный код

    D:\wamp\www\tools\yii\framework\db\CDbCommand.php(375)
    ——————————————————-

    Кстати ). Советую на страницу первого поста поместить информацию, что в файле конфигурации Apache надо сделать изменения для правильных url.

    • Вы используете версию 1.1 фреймворка? В ней есть изменения в работе с базой.
      Этот пример я тестировал на 1.0.10.

  • kaiser Zaido

    Привет, очень интересно.
    А где можно посмотреть на сам сайт?

  • kaiser Zaido

    Привет, очень интересно.
    А где можно посмотреть на сам сайт?

  • Денис

    Владимир, спасибо за ответы )

    Подскажи, а можно в yii путь типа:
    http://mysite.ua/post/5/article.html

    заменить на:
    http://mysite.ua/article.html

    сейчас правило для парсинга этого urla выглядит так:
    'post//'=>array('post/view', 'urlSuffix'=>'.html', 'caseSensitive'=>false),

    п.с. Скажи, а в твоем блоге название статьи автоматом переводится с русского в транслит?

    • Если не использовать ЧПУ, то в каком параметре у вас будет передаваться article.html?
      Лучше покажите как выглядит вообще структура ссылки без ЧПУ.

      P.S. Да, автоматически. Плагин называется Rus-to-Lat.

      • Денис

        Без ЧПУ:
        http://mysite.ua/index.php?r=post/view&id=5&title=article
        (написал по памяти, надеюсь, не ошибся)

        Собственно требуется скрыть все _GET-параметры, а значение параметра title использовать как адрес. (причем с добавлением .html для гуглябота). Т.е. чтоб получилось как у Вас 😉

        • Так не получится?

          '<title>'=>array('post/view', 'urlSuffix'=>'.html', 'caseSensitive'=>false),

        • Денис

          Ура-а!

          Наконец-то получилось. Надо было:

          1. Изменить конфиг

          ''=>array('post/view', 'urlSuffix'=>'.html', 'caseSensitive'=>false,)

          2. Поменять loadModel()

          $this->_model=Post::model()->find("title = :title", array(":title" => $_GET['title']));

          Оказуется все так просто )

        • А как до этого выглядела loadModel()?

  • Денис

    Владимир, спасибо за ответы )

    Подскажи, а можно в yii путь типа:
    http://mysite.ua/post/5/article.html

    заменить на:
    http://mysite.ua/article.html

    сейчас правило для парсинга этого urla выглядит так:
    'post//'=>array('post/view', 'urlSuffix'=>'.html', 'caseSensitive'=>false),

    п.с. Скажи, а в твоем блоге название статьи автоматом переводится с русского в транслит?

    • Если не использовать ЧПУ, то в каком параметре у вас будет передаваться article.html?
      Лучше покажите как выглядит вообще структура ссылки без ЧПУ.

      P.S. Да, автоматически. Плагин называется Rus-to-Lat.

      • Денис

        Без ЧПУ:
        http://mysite.ua/index.php?r=post/view&id=5&title=article
        (написал по памяти, надеюсь, не ошибся)

        Собственно требуется скрыть все _GET-параметры, а значение параметра title использовать как адрес. (причем с добавлением .html для гуглябота). Т.е. чтоб получилось как у Вас 😉

        • Так не получится?

          '<title>'=>array('post/view', 'urlSuffix'=>'.html', 'caseSensitive'=>false),
        • Денис

          Ура-а!

          Наконец-то получилось. Надо было:

          1. Изменить конфиг

          ''=>array('post/view', 'urlSuffix'=>'.html', 'caseSensitive'=>false,)

          2. Поменять loadModel()

          $this->_model=Post::model()->find("title = :title", array(":title" => $_GET['title']));

          Оказуется все так просто )

        • А как до этого выглядела loadModel()?

  • Подскажите пожалуйста как решить проблему.

    Все установил.
    Сайт работает, но при заходе в админку (http://localhost/dashboard)
    выдает следующее:

    Описание

    date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'Asia/Yekaterinburg' for 'YEKST/6.0/DST' instead
    Исходный код

    /srv/http/framework/views/ru/log.php(19)

    00007:
    00008:
    00009: Время
    00010: Уровень
    00011: Категория
    00012: Сообщение
    00013:
    00014: $log)
    00016: {
    00017: $color=($index%2)?'#F5F5F5':'#EBF8FE';
    00018: $message=».CHtml::encode(wordwrap($log[0])).»;
    00019: $time=date('H:i:s.',$log[3]).(int)(($log[3]-(int)$log[3])*1000000);
    00020:
    00021: echo <<<EOD
    00022:
    00023: {$time}
    00024: {$log[1]}
    00025: {$log[2]}
    00026: {$message}
    00027:
    00028: EOD;
    00029: }
    00030: ?>
    00031:

    • И выделена 19 строка.

      • Это предупреждение появляется если установлен E_STRICT в параметре error_reporting (php.ini).
        Данное сообщение предупреждает о том, что скрипт вызвал какую-то из функций, которая возвращает системное время, но предварительно не была указана часовая зона. В теории это может вызвать проблемы, например, если вы передаете данные между серверами, которые находятся в разных часовых поясах.

        Исправить можно двумя способами.
        1) С помощью функции date_default_timezone_set.
        2) Установив параметр date.timezone в php.ini.

  • Подскажите пожалуйста как решить проблему.

    Все установил.
    Сайт работает, но при заходе в админку (http://localhost/dashboard)
    выдает следующее:

    Описание

    date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected 'Asia/Yekaterinburg' for 'YEKST/6.0/DST' instead
    Исходный код

    /srv/http/framework/views/ru/log.php(19)

    00007:
    00008:
    00009: Время
    00010: Уровень
    00011: Категория
    00012: Сообщение
    00013:
    00014: $log)
    00016: {
    00017: $color=($index%2)?'#F5F5F5':'#EBF8FE';
    00018: $message=».CHtml::encode(wordwrap($log[0])).»;
    00019: $time=date('H:i:s.',$log[3]).(int)(($log[3]-(int)$log[3])*1000000);
    00020:
    00021: echo <<<EOD
    00022:
    00023: {$time}
    00024: {$log[1]}
    00025: {$log[2]}
    00026: {$message}
    00027:
    00028: EOD;
    00029: }
    00030: ?>
    00031:

    • И выделена 19 строка.

      • Это предупреждение появляется если установлен E_STRICT в параметре error_reporting (php.ini).
        Данное сообщение предупреждает о том, что скрипт вызвал какую-то из функций, которая возвращает системное время, но предварительно не была указана часовая зона. В теории это может вызвать проблемы, например, если вы передаете данные между серверами, которые находятся в разных часовых поясах.

        Исправить можно двумя способами.
        1) С помощью функции date_default_timezone_set.
        2) Установив параметр date.timezone в php.ini.

  • Владимир, большое спасибо, проблема разрешилась.

    Замечательный блог! Успеха вам)

  • Владимир, большое спасибо, проблема разрешилась.

    Замечательный блог! Успеха вам)

  • TomDeLonge

    Здравствуйте. скажите, а сколько может стоить такой движок, если делать под заказ? никак не могу научится оценивать проекты 🙁

  • Работы здесь примерно часов на 20 (если есть готовый дизайн). Цифра, конечно, очень приблизительная и сильно зависит от ТЗ.
    Если брать почасовую ставку 10$, то получится 200$.

  • Info

    автор, отпиши плиз как вот это перенести на 1.1.x
    если бы в codeigniter была реализация генерации контроллеров как тут, и виджеты — он был бы круче и понятнее

  • Готово 🙂
    Основная проблема при переходе на новую версию касалась вывода игр по жанрам. О том, как ее решить читайте здесь.

  • Игорь

    Здравстуйте. Вопрос глупый, но обязательно ли оставлять надпись Powered by Yii Framework в футере страницы?

  • Вообще-то я не юрист.
    Yii распространяется по BSD лицензии, это означает, что вы можете создавать как бесплатные, так и коммерческие приложения на его основе.
    Кроме того, необходимо сохранить записи о копирайтах в исходниках.
    О том, должна ли быть ссылка на сайте — в лицензии не сказано ничего. Т.е. ее можно убрать.

  • Ownernet

    '/'=>'/view',
    '//'=>'/',
    '/'=>'/',
    »=>'p/'
    Правильно ли такую запись делать?
    Для того, что бы category|page|menu|site обработались бы как обычно, а все остальные {REQUEST_URI} ушли бы на контроллер p

    • По-моему, строку
      »=>'p/'
      нужно поставить первой. Но я не проверял 😉
      В любом случае, подход правильный.

  • Ownernet

    Прошу прощения
    '/'=>'/view',
    '//'=>'/',
    '/'=>'/',
    »=>'p/'
    Правильно ли такую запись делать?
    Для того, что бы category|page|menu|site обработались бы как обычно, а все остальные {REQUEST_URI} ушли бы на контроллер p

  • None

    А как вообще с админкой дела обстоят, как в yii это реализуется?

  • bogdan

    Xоxe чтобы урлы были красивые при переходе в категорию, т.е. адрес был такого вида:

    http://images/category/Авто

    Как сконфигурировать правило?

    Так ссылка генерится:
    'url'=>array('/images/index',array('id' => $val->id, 'category'=>$val->name)));

  • Mike

    Здравствуйте , не подскажете как сделать чтобы шаблон не съезжал вниз на странице с играми??он съезжает от верха страницы где то на 30-35 px.притом на странице с контактами он стоит прям в верху.
    коды css одни и теже. уже не знаю куда копать..

    • Можете дать ссылку на ваш сайт? Мне будет гораздо проще определить проблему с помощью firebug 😉

  • Arroyo

    Когда появится пример разработки сайта на YII 2?

    • Пока сроки назвать не могу, но статьи по Yii2 планирую.