Askeet. День девятый. Локальные улучшения
Приветствуем!
Хотите что-то написать?Нужно назвать себя.
Если вы пришли в первый раз,
то нужно зарегистрироваться.
Переводы → Askeet. День девятый. Локальные улучшения
Предисловие
В течение восьмого дня, мы добавили AJAX в "Спроси-ка" без головной боли. Приложение теперь готово к использованию, но его можно сделать удобнее. Для вопросов должен быть разрешён текстовый редактор, а первичные ключи не стоит использовать в URI. Всё это не трудно сделать с помощью symfony: сегодня будет хорошая возможность использовать то, чему научились, и проверить знаете ли Вы как управлять всеми уровнями архитектуры MVC.
Поддержка текстового редактора в вопросах и ответах
Markdown
Поля для вопросов и ответов сечас поддерживают только простой текст. Для поддержки стандартного форматирования - жирный, курсив, ссылки, изображения и т.д. - мы будем использовать стороннюю библиотеку, а не изобретать велосипед.
Если вы смотрели документацию по symfony в текстовом формате, возможно вы заметили, что мы большие фанаты Markdown. Markdown - это утилита для преобразования текста в HTML, имеющая свой синтаксис для форматирования текста. Большое преимущество Markdown над, например, синтаксисом Wiki или форумов, это то, что обычный тестовый файл markdown очень легко читается:
Test Markdown text
------------------
This is a **very simple** example of [Markdown][1].
The best thing about markdown is its _auto-escape_ feature for code chunks:
<a href="http://www.symfony-project.com">link to symfony</a>
>The `<` and `>` are properly escaped as `<` and `>`,
>and are not interpreted by any browser
[1]: http://daringfireball.net/projects/markdown/ "Markdown"
Markdown выведет:
Test Markdown text
This is a very simple example of Markdown. The best thing about markdown is its auto-escape feature for code chunks:
<a href="http://www.symfony-project.com">link to symfony</a>The
<and>are properly escaped as<and>, and are not interpreted by any browser
Библиотека Markdown
Хотя Markdown изначально написан на Perl, он доступен как библиотека PHP Markdown. Её то мы и будем использовать. Скачайте файл markdown.php и положите в папку lib проекта "Спроси-ка" (askeet/lib/). Вот и всё: Теперь она доступна из любого класса проекта "Спроси-ка", при условии что вы её сначала подключите:
require_once('markdown.php');
Мы могли бы вызывать конвертер Markdown каждый раз, когда отображаем сообщение, но это будет слишком нагружать наши серверы. Лучше мы конвертируем текст в HTML на этапе создания вопроса, и будем хранить HTML версию в таблице Question. Возможно Вы используете это, так что расширение модели не будет для Вас сюрпризом.
Расширение модели
Сначала добавим колонку в таблицу Question в schema.xml:
<column name="html_body" type="longvarchar" />
Затем пересоздадим модель и обновим базу данных:
$ symfony propel-build-model
$ symfony propel-build-sql
$ symfony propel-insert-sql
Перегрузка метода setBody
Когда вызывается метод ->setBody() класса Question, колонка html_body должна также обновиться, путём преобразования Markdown'ом текстового поля. Откройте файл модели askeet/lib/model/Question.php и создайте:
public function setBody($v){ parent::setBody($v); require_once('markdown.php'); // strip all HTML tags $v = htmlentities($v, ENT_QUOTES, 'UTF-8'); $this->setHtmlBody(markdown($v));}
Применение функции htmlentities() перед установкой HTML-поля защищает "Спроси-ка" от атак cross-site-scripting (XSS) - все тэги <script> экранируются.
Обновление тестовых данных
Мы добавим форматирование Markdown в некоторые вопросы в тестовых данных (в askeet/data/fixtures/test_data.yml), чтобы проверить корректное пробразование:
Question:
q1:
title: What shall I do tonight with my girlfriend?
user_id: fabien
body: |
We shall meet in front of the __Dunkin'Donuts__ before dinner,
and I haven't the slightest idea of what I can do with her.
She's not interested in _programming_, _space opera movies_ nor _insects_.
She's kinda cute, so I __really__ need to find something
that will keep her to my side for another evening.
q2:
title: What can I offer to my step mother?
user_id: anonymous
body: |
My stepmother has everything a stepmother is usually offered
(watch, vacuum cleaner, earrings, [del.icio.us](http://del.icio.us) account).
Her birthday comes next week, I am broke, and I know that
if I don't offer her something *sweet*, my girlfriend
won't look at me in the eyes for another month.
Теперь вы можете заново заполнить базу данных:
$ php batch/load_data.phpИзменение шаблонов
Шаблон showSuccess.php модуля question может быть красиво изменён:
...<div class="question_body"> <?php echo $question->getHtmlBody() ?></div>...
Фрагмент шаблона списка (_list.php) также отображает тело, но в обрезаном формате:
<div class="question_body"> <?php echo truncate_text(strip_tags($question->getHtmlBody()), 200) ?></div>
Теперь всё готово для финального теста: отобразить три изменённые страницы и посмотреть отформатированный текст из тестовых данных:
http://askeet/question/list
http://askeet/recent
http://askeet/question/show/stripped_title/what-shall-i-do-tonight-with-my-girlfriend

Тоже самое сделаем для Answer body: создадим дополнительную колонку html_body в моделе, перегрузим метод ->setBody() и использовать для отображения ответов в question/show метод ->getHtmlBody() вместо ->getBody(). Т.к. код абсолютно идентичен вышенаписаному, мы не будет здесь его описывать, но Вы сможете его найти в сегодняшнем SVN коде.
Скрываем id-шники
Другой хороший приём в symfony - это как по возможности избавляться от первичных ключей в параметрах запроса. Наши первичные ключи автоматически увеличиваются и это даёт взломщику некоторую информацию о записях в базе данных. Плюс, отображаемые URI ничего не значат, и это плохо для поисковой оптимизации.
Возьмём, например, страницу пользователя. Сейчас она использует id пользователя как параметр. Но если мы уверены, что nickname уникален, мы можем использовать его в качестве параметра для запроса. Так сделаем же это!
Изменим экшн
Отредактируем экшн user/show:
public function executeShow(){ $this->subscriber = UserPeer::retrieveByNickname($this->getRequestParameter('nickname')); $this->forward404Unless($this->subscriber); $this->interests = $this->subscriber->getInterestsJoinQuestion(); $this->answers = $this->subscriber->getAnswersJoinQuestion(); $this->questions = $this->subscriber->getQuestions();}
Изменим модель
Добавим следущий метод в класс UserPeer в директории askeet/lib/model/.
public static function retrieveByNickname($nickname){ $c = new Criteria(); $c->add(self::NICKNAME, $nickname); return self::doSelectOne($c);}
Изменим шаблон
Страница, которая отображает ссылку на профиль пользователя сейчас должна содержать nickname пользователя вместо его id.
В шаблонах question/showSuccess.php и question/_list.php замените:
<?php echo link_to($question->getUser(), 'user/show?id='.$question->getUserId()) ?>
на:
<?php echo link_to($question->getUser(), 'user/show?nickname='.$question->getUser()->getNickname()) ?>
Те же изменения сделайте в шаблоне answer/_answer.php.
Добавление правила маршрутизации
Добавим новое правило в настройки маршрутизации для этого экшна так, чтобы шаблон url содержал параметр nickname:
user_profile:
url: /user/:nickname
param: { module: user, action: show }
После symfony clear-cache можете проверить изменения.
Маршрутизация
Многие экшны, написанные ранее, всё ещё используют стандартную маршрутизацию, содержащую имя модуля и имя экшна в адресной строке браузера. Теперь вы знаете как это поправить, так давайте определим URLы для всех экшнов. Отредактируйте askeet/apps/frontend/config/routing.yml:
# question
question:
url: /question/:stripped_title
param: { module: question, action: show }
popular_questions:
url: /index/:page
param: { module: question, action: list, page: 1 }
recent_questions:
url: /recent/:page
param: { module: question, action: recent, page: 1 }
add_question:
url: /add_question
param: { module: question, action: add }
# answer
recent_answers:
url: /recent/answers/:page
param: { module: answer, action: recent, page: 1 }
# user
login:
url: /login
param: { module: user, action: login }
logout:
url: /logout
param: { module: user, action: logout }
user_profile:
url: /user/:nickname
param: { module: user, action: show }
# default rules
homepage:
url: /
param: { module: question, action: list }
default_symfony:
url: /symfony/:action/*
param: { module: default }
default_index:
url: /:module
param: { action: index }
default:
url: /:module/:action/*
Если Вы просматриваете в production environment, настоятельно рекомендуем Вам почистить кэш перед проверкой этих изменений.
Один хороший приём в маршрутизации symfony - это использование имён правил в хелпере link_to() вместо module/action. Это не только быстрее (движок маршрутизатора не должен просматривать всю таблицу маршрутизации, чтобы найти правило), но так же позволяет Вам изменять экшн независимо от имени правила. В главе Links and the Routing System из книги по symfony рассказывается об этом подробнее.
<?php link_to('@user_profile?id='.$user->getId()) ?>// is better than<?php link_to('user/show?id='.$user->getId()) ?>
"Спроси-ка" следует хорошим приёмам symfony, поэтому код, который Вы скачаете в конце сегодняшнего урока, содержит только имена правил в ссылочных хелперах. Замена action/module на @rule во всех шаблонах и своих хелперах не очень прикольное занятие, поэтому последний совет про маршрутизацию: пишите правила маршрутизации как только создаёте экшн и используйте имена правил в ссылочных хелперах с самого начала.
Увидимся завтра
Сегодняшние изменения слишком большие, чтобы прочитать и понять. К тому же, изменения описанные в учебнике были повторены в подобных случаях во всём коде. Хотя ничего нового сегодня не добавилось, код сильно изменился.
Если Вам кажется, что сегодня Вы не узнали о symfony ничего нового, это значит, что вы готовы запускать свой собственный проект. Процесс создания экшна, изменяя модель как нужно, написание простого шаблона для вывода экшна и редактирование конфигурации для вставки экшна в логику приложения - основа разработки на symfony.
Все хорошие примеры, рассмотренные здесь (использование внешних библиотек вместо переписывания на symfony, не показывать первичные ключи в приложении, использование имён правил вместо module/action) сделают ваше приложение чистым, безопасным и быстрым.
Но приложение "Спроси-ка" далеко от финиша! Недостающая функциональность - это добавление нового вопроса и ответа. Этим мы и займёмся завтра.
У Вас есть идеи, что можно добавить в 21 день? Отправляйте всё на почту "Спроси-ка". Так держать!




