Askeet. День третий. Погружение в структуру MVC
Приветствуем!
Хотите что-то написать?Нужно назвать себя.
Если вы пришли в первый раз,
то нужно зарегистрироваться.
Переводы → Askeet. День третий. Погружение в структуру MVC
Ранее на symfony
В течение второго дня вы научились строить объектную модель, основанную на реляционной модели данных, а так же генерировать шаблоны для этих объектов. Кстати, код, созданный за предыдущие дни, доступен в репозитарии SVN:
Задачами третьего дня стали: создание симпатичного внешнего вида для сайта, назначение списка вопросов как главную страницу, отображение количества заинтересовавшихся пользователей для вопроса и население базы данных примерами текстовых файлов для того чтобы иметь тестовые данные. Не так уж и много делать, но придётся много читать, думать и понимать.
Прежде чем продолжить чтение данной главы, вы должны ознакомиться с концепцией проекта, приложений, модулей и действий в symfony. Это объясняется в главе Inside the Controller Layer из книги по symfony.
Структура MVC
Сегодня наш первый день погружения в архитектуру MVC. Что это значит? Просто напросто то что код, который обычно создаёт одну страницу, разположен в разных файлах согласно своей природе.
Если код содержит управление данных не зависящей от страницы, то он должен быть расположен в Модели(Model). Если подразумевается финальное отображение, то оно должно быть расположено в Виде(View). В symfony, Вид основан на шаблонах (например /apps/frontend/modules/question/templates/) и конфигурационных файлах. Код, связывающий это всё вместе и переводящий в старый добрый PHP, расположен в Контроллере (Controller). В symfony контроллер для определённой страницы вызывается в действии (посмотрите на действия в /apps/frontend/modules/question/actions/). Вы можете узнать больше о данной модели в главе Exploring Symfony's Code из книги по symfony.
Сегодня мы лишь немного изменим внешний вид нашего приложения, но нам придётся манипулировать огромных количеством файлов. Не надо паниковать - организация файлов и разделение кода на различных уровнях скоро вам покажется очевидным и весьма полезным.
Меняем внешний вид.
При использовании паттерна программирования "Декоратор" содержимое шаблона, вызываемое действием, внедряется в общий шаблон или обрамление. Другими словами, обрамление содержит в себе не изменяющиеся части интерфейса, оно декорирует результаты действий. Откройте стандартный декоратор (apps/frontend/templates/layout.php) и измените его на следующее
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head> <?php echo include_http_metas() ?><?php echo include_metas() ?> <?php echo include_title() ?> <link rel="shortcut icon" href="/favicon.ico" /> </head><body> <div id="header"> <ul> <li><?php echo link_to('about', '@homepage') ?></li> </ul> <h1><?php echo link_to(image_tag('askeet_logo.gif', 'alt=askeet'), '@homepage') ?></h1> </div> <div id="content"> <div id="content_main"> <?php echo $sf_data->getRaw('sf_content') ?> <div class="verticalalign"></div> </div> <div id="content_bar"> <!-- Nothing for the moment --> <div class="verticalalign"></div> </div> </div> </body></html>
Мы пытались соблюдать маркировки настолько сематичной, насколько это возможно и перенесли все стили в CSS файлы. Файлы стилей мы описывать не будем - у нас нет такой цели в данном учебнике. Тем не менее вы их можете скачать из репозитария SVN. Мы создали 2 файла стилей (main.css и layout.css). Скопируйте их себе в каталог /web/css/ и отредактируйте frontend/config/view.yml, чтобы добавить стили в автозагрузку:
stylesheets: [main, layout]
Данная обёртка пока ещё лёгкая. Позже мы её перестроим (где-то через неделю). Важными частями в данном шаблоне являются <head>, который обычно генерируется и переменная sf_content, которая содержит в себе результат действий.
Проверьте, отображаются ли измненения по адресу среду разработки:
http://askeet/frontend_dev.php/

Несколько слов про среды
Если вам интересна разница между http://askeet/frontend_dev.php/ и http://askeet/, то вам наверное стоит заглянуть в главу про конфигурирование в книге по symfony. Сейчас вы лишь должны знать о том, что они указывают на одно и тоже приложение, но на разные среды. Среда это уникальная конфигурация, где некоторые возможности фреймворка могут быть активированны или деактивированны.
В данном случае /frontend_dev.php/ указывает на среду разработки, где вся конфигурацию обновляется при каждом запросе, кэш HTML деактивирован, а так же доступны все инструменты отладки (включая полупрозрачную панель управления, расположенную в верхнем правом углу окна). Ссылка / равносильна /index.php/ и указывает на продукционную среду, где конфигурацию компилируется однажды и инструменты отладки отключены для ускорения загрузки страницы.
Эти два PHP скрипта - frontend_dev.php и index.php - называются фронт-контроллерами и все запросы к приложению управляются через них. Вы можете найти их в каталоге /web/. По идее index.php должен называться frontend_prod.php, но фронтенд это первое приложение, которое вы созали и symfony решил что этот файл стоит переименовать в index.php, чтобы вы могли увидеть своё приложение всего лишь введя /. Если вы хотите узнать больше о фронт-котроллерах и контроллерах в модели MVC в целом, то прочтите главу про контроллеры в книге по MVC.
Возьмите за правило использовать только среду разработки, до тех пор, пока не будете удовлетворены результатом, а потом уже проверить продукционную среду для проверки красоты и скорости.
Не забывайте чистить кэш после добавления новых классов или изменения конфигурация, иначе не увидите изменений в продукционной среде.
Изменим главную страницу
Сейчас, если вы запросите главную страницу нового сайта, то увидите страницу с поздравлениями. Гораздо было бы лучше отобразить список вопросов (ссылкой на который является question/list и означающей - действие "list"(список) в модуле "question"(вопросы). Для того чтобы сделать это откройте конфигурационный файл путей, который расположен в /apps/frontend/config/routing.yml и найдите часть с homepage. Замените её на этот код:
homepage: url: / param: { module: question, action: list }
Обновите домашнюю страницу в среде разработки (http://askeet/frontend_dev.php/<). Теперь она показывает список вопросов
Если вы заинтересованный человек, то вы наверное вчитались в сообщение на странице приветствия. Наверное вы удвились, не увидев его в каталоге со "Спросика". Данный шаблон для действия default/index определён в папке с symfony и независим от проекта. Если хотите его переписать, то можете просто создать модуль default в своём проекте.
Возможности системы путей будут описаны в будущем, но если вам уж очень инетерсно, то можете прочитать главу про систему путей в книге по symfony.
Создание тестовых данных
Список на главной странице может долго оставаться пустым, если не внести свои вопросы. При разработке приложения неплохо было бы ввести некоторые тестовые данные на ваше усмотрение. Ввод тестовых данных от руки (через интерфейс CRUD или же прямо через базу данных) может оказаться болезненным, поэтому symfony может использовать текстовые файлы для заполнения базы данных.
Мы создадим тестовые данные в каталоге /data/fixtures/ (этот каталог придётся создать). Создайте файл test_data.yml и введите следующее:
User: anonymous: nickname: anonymous first_name: Anonymous last_name: Coward fabien: nickname: fabpot first_name: Fabien last_name: Potencier francois: nickname: francoisz first_name: François last_name: ZaninottoQuestion: 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 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. q3: title: How can I generate traffic to my blog? user_id: francois body: | I have a very swell blog that talks about my class and mates and pets and favorite movies.Interest: i1: { user_id: fabien, question_id: q1 } i2: { user_id: francois, question_id: q1 } i3: { user_id: francois, question_id: q2 } i4: { user_id: fabien, question_id: q2 }
Я думаю вы заметили формат YAML Если вы ещё не связаны крепко с symfony, то думаю вы ещё не знаете что YAML это любимый формат для конфигурационных файлов этого фреймворка. Но это не исключение - если вы прикрутите XML или .ini файлы, то будет очень просто добавить конфигураторы для их чтения. Если у вас есть время и терпение, то прочтите про YAML и файлы конфигурации symfony в главе о конфигурации в практике из книги по symfony. Если вы ещё не связывались с синтаксисом YAML, то лучше начать прямо сейчас, т.к. в данном уроке мы будем его применять очень часто.
Так, вернёмся к нашему файлу с данными. Он определяет объекты, обозначенные внутренним именем. Обозначение очень полезно для создания ссылки на привязанные элементы без определения id'шников (которые обычно автоматически увеличиваются и не могут быть установлены напрямую). Например, первый созданный объект относится к классу User и обозначен как fabien. Первый вопрос обозначен как q1. Это позволяет с лёгкостью создать объект класса Interest, указав привязанные объекты:
Interest: i1: user_id: fabien question_id: q1
Предыдущий файл, использующий короткий синтаксис YAML, пытается сказать тоже самое. Вы можете узнать больше о файлах данных в главе про файлы данных из книги по symfony.
Нет нужды задавать значения для полей created_at и updated_at, т.к. symfony прописывает стандартные значения.
Создание скрипта для заполнение базы данных
Следующим шагом мы будем заполнять базу данных и мы хотим сделать это через PHP скрипт, который может быть вызван через коммандную строку.
Каркас скрипта
Создайте файл load_data.php в каталоге askeet/batch/ и внесите следующее:
<?php define('SF_ROOT_DIR', realpath(dirname(__FILE__).'/..'));define('SF_APP', 'frontend');define('SF_ENVIRONMENT', 'dev');define('SF_DEBUG', true); require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php'); // initialize database manager$databaseManager = new sfDatabaseManager();$databaseManager->initialize(); ?>
Скрипт абсолютно ничего не делает, он лишь определяет путь, приложение и среду, для того чтобы добраться до конфигурации, загружает эту конфигураци и инициализирует менеджер баз данных. Казалось бы много - данный код возьмёт на себя автозагрузку классов, автоматически подключится к объектам Propel и основным классам symfony.
Если вы уже смотрели на фронт-контроллеры(вроде /web/index.php), то наверное уже заметили, что код похож. Это лишь потому, что каждый запрос обращается к тем же объектам и конфигурации, так же как и скрипт.
Импорт данных
Раз основа скрипта готова - самое время уже что-то сделать. Скрипт будет:
- Читать YAML файл.
- Инициализировать объекты Propel
- Создавать соотвествующие записи в таблицах базы данных
Звучит немного запутанно, но в symfony вы можете сделать это двумя строками кода (спасибо sfPropelData). Просто добавьте следующий код перед закрывающим ?> в файле /batch/load_data.php:
$data = new sfPropelData();$data->loadData(sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fixtures');
Вот и всё. sfPropelData создан и делает за нас всю чёрную работу - загружает данные из определённого каталога в базу, определённую в databases.yml.
Константа DIRECTORY_SEPARATOR используется для совместимости с платформами *nix и Windows.
Запускаем скрипт
Ну наконец-то! Вы можете проверить полезность этих двух строк кода:
$ cd /home/sfprojects/askeet/batch$ php load_data.php
Проверьте изменения в базе данных обновив главную страницу.

Уррааа! А вот и данные!
sfPropelData всегда удаляет все данные перед загрузкой новых. Но вы можете добавить и текущие данные:
$data = new sfPropelData(); $data->setDeleteCurrentData(false); $data->loadData(sfConfig::get('sf_data_dir').DIRECTORY_SEPARATOR.'fixtures');
Получение даных в модели
Страница, показанная при запросе действия list модуля question это результат работы executeList() (который находится в /apps/frontend/modules/question/actions.class.php) передавшего шаблон /apps/frontend/modules/question/templates/listSuccess.php. Это основано на соглашении об именовании, объясняемом в главе о котроллере из книги по symfony. Давайте взглянем на исполняемый код.
actions.class.php:
public function executeList (){ $this->questions = QuestionPeer::doSelect(new Criteria());}
listSuccess.php:
<?php foreach ($questions as $question): ?><tr> <td><?php echo link_to($question->getId(), 'question/show?id='.$question->getId()) ?></td> <td><?php echo $question->getTitle() ?></td> <td><?php echo $question->getBody() ?></td> <td><?php echo $question->getCreatedAt() ?></td> <td><?php echo $question->getUpdatedAt() ?></td> </tr><?php endforeach; ?>
Действия шаг за шагом:
- Действией получает записи из таблицы вопросов, которые отвечает пустому критерию - все вопросы
- Данный список вопросов помещается в массив ($question), который передаётся шаблону.
- Шаблон пробегается по всем вопросам, переданным действием
- Шаблон отображает значения полей каждой из записей
Функции ->getId(), ->getTitle(), ->getBody(), и т.д. были созданы после команды propel-build-model и отвечают за получение полей id, title, body и т.п. Это стандартные получатели, образованные добавлением префикса get к камелизированному имени поля. Propel так же создаёт и стандартные функции установки значений с префиксом set. В документации по Propel описаны функции доступа для каждого класса.
За мистической функцией QuestionPeer::doSelect(new Criteria()) скрывается обычный запрос Propel. Документация по Propel подробно может это объяснить.
Не переживайте если вы ничего не поняли из вышенаписанного кода, скоро всё станет яснее.
Изменяем шаблон списка вопросов
Так как база данных содержит множество интересных вопросов, то будет легко посчитать количество заинтересованных пользователей для вопроса. Если вы взглянете на класс BaseQuestion.php, созданный Propel'ом, в /lib/model/om/, то должны заметить метод ->getInterests(). Propel увидел внешний ключ question_id в таблице interest и определил, что у вопроса есть несколько интересов. Это позволяет нам с лёгкостью отобразить то что захотим изменив шаблон listSuccess.php, находящийся в apps/frontend/modules/question/templates/. В этом процессе мы заменим ужасающие таблицы на замечательные блоки.
<?php use_helper('Text') ?> <h1>popular questions</h1> <?php foreach($questions as $question): ?> <div class="question"> <div class="interested_block"> <div class="interested_mark" id="mark_<?php echo $question->getId() ?>"> <?php echo count($question->getInterests()) ?> </div> </div> <h2><?php echo link_to($question->getTitle(), 'question/show?id='.$question->getId()) ?></h2> <div class="question_body"> <?php echo truncate_text($question->getBody(), 200) ?> </div> </div><?php endforeach; ?>
Как вы видите здесь точно такой же итератор foreach как и в исходном listSuccess.php. Функции link_to() и truncate_text() это помошники(хелперы) шаблона, определяемые symfony. Первый создаёт ссылку на другое действие этого же модуля, а второй обрезает тело вопроса до 200 символов. Хелпер link_to находится в автозагрузке, но вам придётся придётся указать на загрузку группы хелперов Text, для того чтобы использовать truncate_text().
Давайте! Попробуйте свой новый шаблон, обновив главну страницу по адресу
http://askeet/frontend_dev.php/

К каждому вопросу теперь отображается количество заинтересованных пользователей. Чтобы получить получить такой же внешний вид скачайте стиль main.css и поместите в askeet/web/css/
Зачистка
Команда propel:generate-crud создала некоторые действия, которые нам не понадобятся. Самое время их удалить.
Действия для удаления в apps/frontend/modules/question/actions/actions.class.php:
- executeIndex
- executeEdit
- executeUpdate
- executeCreate
- executeDelete
Так же удалите askeet/apps/frontend/modules/question/templates/editSuccess.php
Ещё увидимся!
Сегодня мы сделали первый большой шаг в мир парадигмы Model-View-Controller - манупулировали обрамлением шаблонов и самим шаблоном, действиями и объектами Propel. Вы смогли поработать со всеми уровнями структуры приложений MVC. Не переживайте если ещё не понятна связь между этими уровнями - мало по малу всё будет проясняться.
Сегодня мы открыли множество файлов, а чтобы не заблудиться в организации проекта - прочитайте главу про структуру из книги по symfony.
А завтра вновь будет великий день - мы будем изменять виды, создавать более сложную политику путей, изменять модельи и копать глубже и глубже в управление данными и ссылками между таблицами.
Коментарии:
Нужно подправить ссылку на "главу про структуру"
http://translated.by/you/the-definitive-guide-to-symfony-for-the-1-1-version-chapter-4-the-basics-of-page-creation/
не буду перечислять все ссылки, но большинство статей по ссылкам уже переведены. надо бы все подправить.
ответить



