Вы находитесь на странице: 1из 86

PHP on Rails Руководство программиста

Содержание

1 Вступление

 

4

1.1

Описание

5

 

1.1.1

Компоненты

5

1.2 Установка

2 Пример создания приложения

2.1 Начальные приготовления

6

8

10

2.1.1 Модель данных

10

2.1.2 Создание базы данных

10

2.1.3 Конфигурирование приложения

10

2.2 Управление товарами

12

2.2.1 Создание таблицы products

12

2.2.2 Создание приложения для управления товарами

12

2.2.3 Модель

14

2.2.4 Контроллер

16

2.2.5 Представление

19

2.2.6 Проверка данных

19

2.2.7 Улучшение интерфейса

22

2.2.8 Удаление товара

26

2.2.9 Добавление нового атрибута

27

2.3 Каталог товаров

30

2.4 Создание покупательской корзины

33

2.4.1 Сессии

33

2.4.2 Новая модель данных

33

2.4.3 Создание покупательской корзины

35

2.4.4 Пример обработки ошибок

41

2.4.5 Пример рефакторинга

41

2.5

Покупка

43

2.5.1 Оформление заказа

43

2.5.2 Вывод содержимого корзины при оформлении заказа

49

2.6 Отправка заказов

52

2.6.1 Просмотр новых заказов

52

2.6.2 Связь «один-ко-многим»

55

2.6.3 Отправка заказов

56

2.7 Управление администраторами

57

2.7.1 Создание администратора

57

2.7.2 Просмотр администраторов

61

2.7.3 Вход в систему

61

2.7.4 Ограничение доступа

65

2.7.5 Удаление администраторов

66

2.7.6 Выход из системы

67

3 Многоязычные приложения

3.1 Установка и конфигурация

69

70

3.1.1 Установка

70

3.1.2 Создание базы данных

70

3.1.3 Конфигурирование базы данных

71

3.2 Создание приложения

73

3.2.1 Структура

73

3.2.2 Переводчик

74

3.2.3 Многоязычные шаблоны

75

3.2.4 Выбор текущего языка

78

3.2.5 Перевод приложения

78

2

3.2.6

Кэширование

83

3.2.7 Визуальное редактирование переводов

3

84

1 Вступление

4

1.1

Описание

PHP on Rails - это развивающаяся многоуровневая фреймворк система, предназначенная для создания средних и сложных веб-приложений, работающих с базами данных и использующих архитектуру Модель-Представление-Контроллер (MVC). PHP on Rails это объектно-ориентированная библиотека, написанная на языке PHP версии 5.

1.1.1

Компоненты

PHP on Rails включает в себя реализацию следующих компонент:

1. Архитектура MVC (Модель-Представление-Контроллер) позволяет создавать хорошо структурированные приложения; в качестве представления используется генератор шаблонов Smarty;

2. Абстрактный доступ к базе данных избавляет разработчиков от необходимости изучать API для работы с различными СУБД и делает приложение более переносимым (текущая версия проверена только для MySQL);

3. ORM (Object Relationship Mapping) делает удобным работу с объектами базы данных, пряча использование языка SQL;

4. Многоязычный интерфейс; PHP on Rails позволяет создавать веб-приложения с многоязычным интерфейсом, используя в качестве хранилища переводов базу данных; простота создания многоязычного интерфейса достигается за счёт расширения возможностей Smarty;

5. Встроенный конструктор сайта избавляет разработчика от рутинной работы, генерируя многие типичные сценарии.

5

1.2

Установка

Для начала необходимо установить нужное программное обеспечение: веб-сервер, PHP 5 и СУБД MySQL. Как устанавливать эти продукты в данном руководстве не объясняется. Разработка и запуск приложений из примеров производились на следующей конфигурации системы:

1. веб-сервер Apache 1.3.29

2. PHP 5.1.5, установленный как модуль веб-сервера mod_php

3. MySQL 5.0.22

Фреймворк PHP on Rails поддерживает различные реляционные базы данных, однако в последующих примерах используется СУБД MySQL.

Замечание! Текущая реализация PHP on Rails поддерживает только MySQL. Поддержку остальных СУБД можно включить, используя PDO. Эта возможность в данном руководстве не рассматривается, т.к. ещё не была протестирована.

Теперь установите PHP on Rails на вашем компьютере. Для этого необходимо распаковать содержимое инсталляционного архива в какую-либо директорию. Назовите эту директорию rails_demo. Она включает в себя саму фреймворк систему и каркас будущего приложения. Внутри неё должны находиться следующие файлы и директории.

следующие файлы и директории . В директории app содержаться

В директории app содержаться все компоненты MVC архитектуры данного приложения: модели, представления и контроллеры,- а также дополнительные классы и скрипты.

Очень важно! Веб-сервер должен иметь права записи в поддиректории app/views/cache и app/views/templates_c.

Директория config содержит конфигурационные файлы приложения.

В директорию log пишутся все логи приложения, в том числе и ошибки уровня PHP. Веб-сервер должен иметь права записи в эту директорию.

Директория public содержит все ресурсы веб-приложения, которые должны быть доступны извне, т.е. некоторые PHP скрипты, файлы html, изображения и т.д. Только эта директория должна быть доступна извне (по протоколу http(s))!

Директория system это «сердце» системы, непосредственно сама библиотека PHP on Rails.

Файлы app.init.php и app.finalize.php выполняются соответственно в начале и в конце запуска сценария приложения.

Для удобства создаваемое приложение будет располагаться по адресу http://rails.loc на вашем локальном компьютере. Для этого нужно прописать в файле hosts строку

6

127.0.0.1 rails.loc

, а в конфигурационный файл Apache добавить следующую запись

NameVirtualHost 127.0.0.1:80

<VirtualHost rails.loc:80> ServerAdmin webmaster@rails.loc DocumentRoot <www path>/rails_demo/public ServerName rails.loc ErrorLog logs/rails-error_log CustomLog logs/rails-access_log common </VirtualHost>

Перезапустите свой веб-сервер и наберите в браузере URL http://rails.loc/. Если всё установлено правильно, то вы должны увидеть следующую страницу. При её запуске не выполняется никаких полезных действий. Таблица, которую вы видите, содержит отладочную информацию.

. Таблица , которую вы видите , содержит отладочную информацию . 7

7

2 Пример создания приложения

8

В этой главе объясняется, как при помощи PHP on Rails быстро создать небольшое приложение, и демонстрируются основные возможности фреймворка. В качестве демонстрации было выбрано приложение Depot из книги «Agile Web Development with Rails», где главным инструментом разработки является фреймворк Ruby on Rails.

Данное приложение это несложный Интернет магазин. Клиент просматривает каталог товаров на главной странице сайта, добавляет понравившиеся ему товары в Покупательскую корзину и после всего оформляет заказ. Администратор магазина имеет возможность добавлять, изменять и удалять товары, а также принимать заказы, оформленные покупателями.

9

2.1

Начальные приготовления

2.1.1 Модель данных

Для начала надо определить, какие объекты данных необходимы. Из поставленной задачи ясно, что большинство операций производится над товарами. Поэтому одним из типов объектов будет Product. Каждый продукт характеризуется именем, подробным описанием и ценой.

Товары добавляются в Покупательскую корзину Cart. Корзина должна содержать следующие данные: товар, цена, по которой он был куплен, и количество единиц данного товара. Возникает вопрос, зачем дублировать информацию о цене товара: она хранится как атрибут товара и в Покупательской корзине. Это сделано для того, чтобы если цена товара изменится уже после оформления заказа, то это не отразится на стоимости всего заказа. Ведь никому не понравится заказать товар по одной цене, а при его получении заплатить больше.

Поскольку уже несколько раз говорилось о заказах, то, очевидно, что это ещё один претендент в модель данных - Order. Заказ содержит информацию о покупателе и оплате.

Начальная модель данных представлена на диаграмме.

представлена на диаграмме . 2.1.2 Создание базы данных Как

2.1.2 Создание базы данных

Как минимум в приложении понадобится база данных для хранения всех товаров. Назовите её depot.

CREATE DATABASE depot

Помещайте все SQL команды в файл app/sql/structure.sql.

2.1.3 Конфигурирование приложения

Вся конфигурация приложения хранится в файлах, расположенных в папке config:

config.php и application.cfg.xml.

В файле config.php определены две переменные: $DEBUG и $BASE_URL. Во время разработки и отладки приложения значение переменной $DEBUG рекомендуется устанавливать в true. Это включает вывод таблицы с отладочной информацией и

10

сообщений об ошибках. Переменная $BASE_URL должна соответствовать базовому URL приложения с символом “/” на конце. В случае данного приложения это http://rails.loc/.

Файл application.cfg.xml представляет собой XML-документ, содержащий настройки базы данных и ORM. В первоначальном варианте он включает две записи две конфигурации соединений к базам данных.

<?xml version="1.0" encoding="UTF-8"?>

<Databases>

<!--

This database is used by Site Builder Do not delete it

--> <Database name="server"> <Driver>MySQL</Driver> <Host>localhost</Host>

<Port>3306</Port>

<User>root</User>

<Password></Password>

</Database>

<Database name="database"> <Driver>MySQL</Driver> <Host>localhost</Host>

<Port>3306</Port>

<User>root</User>

<Password></Password>

<Database>database</Database>

</Database>

<Mapping>

<MapDir>./config/ormmapping/</MapDir>

</Mapping>

</Databases>

Первую запись лучше не трогать, как это указано в комментариях. Только если пароль для пользователя root у вас не пустой, то укажите его между соответствующих тегов. Вторую запись измените так, чтобы она соответствовала параметрам соединения с базой depot. Она должна выглядеть следующим образом.

<Database name="depot_db"> <Driver>MySQL</Driver> <Host>localhost</Host>

<Port>3306</Port>

<User>depot_user</User>

<Password>paroli</Password>

<Database>depot</Database>

</Database>

Каждый элемент Database в корне XML документа имеет атрибут name, который является индексом базы данных, чем-то вроде уникального имени базы внутри приложения. Не может быть нескольких элементов Database с одинаковыми индексами. Индекс базы данных depot равен “depot_db”.

На этом конфигурация приложения закончена.

11

2.2

Управление товарами

2.2.1 Создание таблицы products

Основной моделью магазина является Товар (Product). Для хранения товаров создайте таблицу products со структурой, отвечающей приведённой ранее модели данных. На языке SQL определение таблицы выглядит следующим образом.

CREATE TABLE `products` ( `id` int(10) unsigned NOT NULL auto_increment, `title` varchar(100) NOT NULL, `description` text NOT NULL, `price` decimal(5,2) NOT NULL, PRIMARY KEY (`id`)

)

Данная таблица содержит название товара, описание и цену, заданную с точностью до сотых. Также в определении таблицы присутствует ещё одно поле: id. Это поле является первичным ключом таблицы и уникально для каждой записи. Желательно, чтобы каждый объект базы данных имел первичный ключ. Также желательно пока точно следовать наименованиям таблиц и полей в приводимых примерах.

2.2.2 Создание приложения для управления товарами

Каждое приложение PHP on Rails содержит раздел, называемый конструктором сайта Site Builder. Он расположен по адресу http://rails.loc/builder/. Этот раздел предназначен для генерации кода приложения. Чтобы конструктор сайтов работал, нужно, чтобы веб-сервер имел права записи в директории проекта (rails_demo) и её поддиректориях. Иначе он не сможет записать в них генерируемые файлы. Это необходимо только на платформе разработчика.

Зайдите по адресу конструктора сайта и выберите в левом меню пункт “Scaffold”. “Scaffold” – это леса, на которых вы можете построить приложение. Чтобы создать часть приложения, управляющую товарами, вам достаточно заполнить появившуюся на странице форму и нажать кнопку “Generate”. Заполните форму следующим образом.

и нажать кнопку “Generate”. Заполните форму следующим образом . 12

12

Сгенерируйте код и наберите в браузере адрес http://rails.loc/admin/. Вы увидите страницу с таблицей просмотра товаров и ссылку “Add new product” («Добавить новый товар»).

product” (« Добавить новый товар »). Перейдите по ссылке « Добавить

Перейдите по ссылке «Добавить новый товар», заполните форму и нажмите кнопку “Save” (Сохранить).

кнопку “Save” ( Сохранить ). После сохранения вы снова

После сохранения вы снова попадёте на страницу просмотра товаров. На этот раз таблица содержит одну строку. В этой строке имеются две ссылки: “Edit” (Редактировать) и “Remove” (Удалить) соответственно для изменения товара и удаления из базы данных.

13

Как видите , не написав ни строчки кода , вы создали работающее

Как видите, не написав ни строчки кода, вы создали работающее приложение. Конструктор сайта выполнил за вас следующие действия:

1. создал модель Product (Товар),

2. создал контроллер admin,

3. добавил в контроллер методы для просмотра, добавления, редактирования и удаления объектов модели Product.

Понятия модель и контроллер в PHP on Rails следует обсудить подробнее.

2.2.3

Модель

Вспомните, как вы заполняли форму “Scaffold”. Перед вами был выбор: создать новую модель или использовать уже существующую.

использовать уже существующую . Следуя указаниям этого

Следуя указаниям этого руководства, вы выбрали первый вариант. Чтобы создать новую модель, вы выбрали индекс базы данных (depot_db), в которой находится таблица, лежащая в основе модели, саму таблицу (products) и указали имя класса, реализующего модель (Product).

“Scaffold”

сгенерировал

два

файла, необходимых модели Product: «карту»

Product.hbm.xml и класс Product.class.php.

14

2.2.3.1

«Карта» модели

Файл Product.hbm.xml находится в каталоге config/ormmapping. Этот файл представляет собой «карту», описывающую таблицу products, класс Product и связь между ними. Именно благодаря этой «карте» PHP on Rails знает, что экземпляр класса Product надо извлекать из таблицы products, знает о её структуре и автоматически конструирует SQL запросы для манипулирования объектом. Файлы *.hbm.xml были позаимствованы из ORM библиотеки для языка Java – Hibernate (hbm в названии файла об этом и говорит).

«Карта»

подробно.

имеет структуру XML документа, которая ниже рассматривается более

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping> <class name="Product" database="depot_db" table="products">

<id name="id" column="id" type="integer"/> <property name="title" column="title" type="string" not-null="true"/> <property name="description" column="description" type="string" not- null="true"/> <property name="price" column="price" type="float" not-null="true"/>

</class>

</hibernate-mapping>

В корне документа находится элемент class. Его атрибуты name, table и database определяют соответственно имена класса, таблицы и индекса базы данных. Очень важно!!! Атрибут name элемента Database из файла конфигурации должен быть равен атрибуту database элемента class из «карты». Индекс базы данных применяется для того, чтобы в случае её переименования, не пришлось изменять атрибут database во всех «картах», которых в приложении могут быть десятки. Достаточно будет изменить имя базы данных в конфигурационном файле application.cfg.xml.

Таким образом, атрибуты элемента class говорят, что экземпляры класса Product извлекаются из базы данных с индексом depot_db, из таблицы products.

Дочерними элементами элемента class являются id и property. Элемент id описывает первичный ключ таблицы. Его атрибуты name, column и type задают имя атрибута, название столбца таблицы и тип данных, поддерживаемый ORM. В текущей реализации PHP on Rails имя атрибута name и название столбца column должны быть равны. Атрибут type имеет 6 значений:

1. integer –целые числа,

2. float – действительные числа,

3. date дата,

4. time - время,

5. datetime – дата и время,

6. string – строковый массив.

В приведённой ниже таблице дано соответствие между типами данных ORM, СУБД MySQL и PHP.

ORM

MySQL

PHP

15

1 integer

integer, smallint, int, tinyint, mediumint, case, bool, bigint, timestamp, year

integer

2 float

numeric, decimal, float, real, double, dec

double

3 date

date

class DateTime

4 time

time

class DateTime

5 datetime

datetime

class DateTime

6 string

char, varchar, tinyblob, tinytext, blob, text, mediumblob, mediumtext, longblob, longtext, enum, set

string

Элементы «карты» с именем property описывают столбцы таблицы (свойства класса). Атрибуты name, type и column у них имеют то же значение, что и у элемента id. Атрибут not-null равен true или false и определяет, может ли данное поле быть пустым, то есть равным NULL в таблице.

2.2.3.2 Класс модели

При работе с моделью Product разработчик манипулирует экземплярами класса Product, определённого в файле app/models/Product.class.php. PHP on Rails реализует концепцию ORM, идея которой состоит в том, что программист работает не с объектами базы данных, а с объектами языка программирования, использует не язык СУБД (SQL), а методы, определённые в обычных классах.

Для каждого своего атрибута (id, title, description, price) класс Product имеет пару set/get методов. Методы load(), save(), remove(), getList() предназначены для извлечения, сохранения и удаления объектов из базы данных. Как их использовать, будет описано дальше.

2.2.3.3 Автозагрузка классов

В PHP on Rails реализован принцип автозагрузки определений классов в

которая автоматически

вызывается, когда в коде упоминается неопределённый ранее класс. Имя этого класса будет передано в качестве единственного аргумента этой функции. В PHP on Rails

ищет определение класса в файле с именем <имя класса>.class.php.

функция

приложение. Для этого используется функция

autoload(),

autoload()

Поэтому класс Product определён в файле Product.class.php, расположенном в директории

app/models. В этой директории приложение хранит классы модели данных.

2.2.4

Контроллер

Контроллер предназначен для реализации бизнес-логики приложения. Так вся логика, связанная с управлением товарами, размещена в контроллере admin. В PHP on Rails контроллер представляет собой класс. Например, контроллер admin реализован в классе AdminController.

Изначально в приложении доступно три контроллера: app (класс AppController), root (RootController) и builder (BuilderController). Класс AppController является предком для всех контроллеров приложения, в свою очередь, являясь потомком класса Controller. Класс RootController обслуживает все запросы пользователя по URL http://rails.loc/. Класс BuilderController реализует контроллер конструктора сайта Site Builder. Он включён в каждое приложение. Все три контроллера можно увидеть в директории app/controllers/.

При генерации контроллера admin конструктор сайта сделал две вещи:

16

1. создал класс AdminController,

2.

в

вызывается веб-сервером на запросы по URL http://rails.loc/admin/.

директории

public

создал

поддиректорию admin с файлом

index.php,

который

Директория public/builder/ не должна быть доступна на рабочем сайте! Не перемещайте её туда с платформы разработчика.

2.2.4.1 Запуск контроллера

Страница для просмотра товаров находится по адресу http://rails.loc/admin/. Из этого адреса можно почерпнуть некоторые важные сведения. После базового URL приложения (http://rails.loc/) указано имя контроллера admin. Запрос обрабатывается файлом PHP public/admin/index.php. Откройте его.

<?PHP

require_once(' / /app.init.php'); require_once(CONTROLLERS_DIR . 'AdminController.class.php'); require_once(SYSTEM_UTILS_DIR . 'Request.class.php');

try {

$controller = new AdminController(); $controller->run(Request::get('action', 'listProducts'));

}

catch (Exception $exception) { ………

}

require_once(' / /app.finalize.php');

?>

Обратите внимание на операторы внутри блока try. В нём создаётся объект класса AdminController и вызывается его метод run() с единственным параметром Request::get(‘action’, ‘listProducts’). Метод get() класса Request возвращает элемент массива $_GET[‘action’], если он задан, или значение по умолчанию ‘listProducts’. Значение параметра action определяет, какую страницу должен обработать контроллер. Фактически метод run() принимает в качестве единственного аргумента имя страницы, запрошенной пользователем.

2.2.4.2 Методы контроллера

Теперь загляните внутрь контроллера admin, определённого в файле app/controllers/AdminController.class.php. В нём кроме конструктора присутствуют методы actionListProducts, actionAddProduct, actionEditProduct, actionRemoveProduct. Методы, начинающиеся на “action”, обрабатывают запросы к контроллеру admin.

Таким образом, URL http://rails.loc/[controller name]/index.php?action=[page name] обрабатывается методом [Controller name]Controller::action[Page name]().

Например, запрос

приведёт к вызову метода actionEditProduct().

17

Внутри метода actionListProducts() находится следующий код.

public function actionListProducts() { $pagination = new ItemPagination(Product::getClassName(), Product::getManager(), null, array('id'), 10, 10, Request::integerParam('page', 1));

/** * Display template */ $this->smarty->assign('pagination', $pagination); $this->smarty->assign('message', $this->getFlash('message')); $this->smarty->display('listProducts.tpl');

}

В первой строке метода создаётся объект класса ItemPagination, который используется для постраничного отображения товаров на странице. Он позволяет просматривать товары не все сразу, а частями, как изображено на рисунке.

, как изображено на рисунке . Конструктор класса ItemPagination

Конструктор класса ItemPagination получает следующие параметры:

1. название класса выводимых объектов;

2. менеджер объектов, который отвечает за извлечение, изменение и удаление объектов из базы данных;

3. условие выборки объектов (выражение WHERE в SQL запросе); в данном примере оно равно NULL, т.е. не установлено;

4. выражение ORDER BY; в данном примере список из одного поля id;

5. количество объектов, отображаемых на одной странице;

6. количество страниц в одном диапазоне, т.е. 1-10, 11-20 (см. рисунок);

7. номер текущей страницы; в данном примере берётся из параметра запроса page и равен 1 по умолчанию.

18

После этого метод контроллера вызывает объект представления.

2.2.5 Представление

После комментария Display template контроллер передаёт объекту представления $this->smarty данные и указывает, какой шаблон использовать для отображения.

Для отображения страниц в браузере используется компилирующий обработчик шаблонов Smarty (http://smarty.php.net). Он позволяет добиться отделения прикладной логики и данных от представления. Если вы не знакомы с данной технологией, то сначала ознакомьтесь с ней, а потом продолжайте чтение данного руководства.

Объект класса AdminController имеет свойство $this->smarty, которое инициализируется в конструкторе. Это свойство является экземпляром класса AdminSmarty (подкласс Smarty), который был сгенерирован вместе с контроллером. Он определён в файле app/views/smarty/AdminSmarty.class.php. В конструкторе класса AdminSmarty указываются директории, в которых хранятся шаблоны страниц и куда сохраняются откомпилированные шаблоны. Библиотека PHP on Rails определяет для этого соответственно директории app/views/templates и app/views/templates_c. Веб-сервер должен иметь права на запись в последнюю директорию.

Шаблон страницы “listProducts” хранится в файле app/views/templates/admin/ listProducts.tpl. Для отображения данных ему был передан объект $pagination.

Код шаблон содержит один нестандартный плагин Smarty: link. Он отвечает за формирование ссылок (тегов <A>) в пределах приложения. В качестве аргументов он принимает имя контроллера и имя страницы (action), также другие дополнительные параметры URL. Если необходимо задать атрибуты HTML тега <A>, то они записываются со знаком “_” спереди.

Например, функция

{link action="editProduct" productId=$product->getId() _id="tagId"} edit {/link}

выводит текст

<a href="index.php?action=editProduct&productId=2" id="tagId">edit</a>

2.2.6 Проверка данных

Попробуйте создать новый товар. Идите по ссылке “Add new product”. Если вы нажмёте кнопку “Save” («Сохранить») в появившейся форме, не заполнив все поля, то товар будет добавлен в таблицу с пустым наименование, пустым описанием и нулевой ценой.

Очевидно, это не то, что вы хотели. Скорее всего, вам нужен механизм проверки формы перед сохранением объекта. Библиотека PHP on Rails позволяет задавать ограничения на свойства объектов. Для этого снова используется XML «карта».

19

Откройте файл config/ormmaping/Product.hbm.xml. Чтобы задать ограничения, нужно в корень XML документа добавить новый элемент validator. Его дочерние элементы задают ограничения свойств.

Например, следующий код

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping> <class name="Product" database="depot_db" table="products"> ……… </class>

<validator> <required property="title">Field title is required</required> <required property="description">Field description is required</required> <required property="price">Field price is required</required> <numeric property="price">Price must be numeric</numeric> <nonzero property="price">Price is nonzero</nonzero> </validator> </hibernate-mapping>

задаёт ограничения:

1. все свойства: title, description и price - обязательны к заполнению (required),

2. свойство price является числовым (numeric),

3. свойство price ненулевое (nonzero).

Каждый элемент, имеет атрибут property, указывающий свойство, на которое накладывается данное ограничение, и содержит сообщение об ошибке.

<ограничение property=”имя свойства”>сообщение об ошибке</ограничение>

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

Проверка формы реализуется в методе actionEditProduct() класса AdminController, который вызывается также методом actionAddProduct().

public function actionAddProduct() { $this->actionEditProduct();

}

public function actionEditProduct() { $productId = Request::param('productId'); if (is_null($productId)) { $product = new Product();

}

else {

$product = Product::load($productId);

}

if (Request::post('save')) { $product->setProperties($_POST);

$errors = array(); $result = Product::getManager()->validate($_POST, Product::getClassName());

if ($result !== true) {

20

$errors = array_merge($errors, $result);

}

if (count($errors) == 0) { $product->save(); $this->setFlash('message', 'Product was saved'); Response::locate(array('action' => 'listProducts'));

}

}

/** * Display template */ $this->smarty->assign('product', $product); if (isset($errors)) { $this->smarty->assign('errors', $errors);

}

$this->smarty->display('editProduct.tpl');

}

В этом методе создаётся объект $product. Он либо извлекается из базы данных, если приложению передаётся параметр productId, либо создаётся новый экземпляр класса Product. Для извлечения объекта из базы данных используется метод load(), в который передаётся единственный аргумент первичный ключ товара в таблице products. Если товар с таким первичным ключом не найден, то метод load() выбрасывает исключение типа ItemNotFound.

Метод Request::post() возвращает значение параметра, переданного методом POST, т.е. берёт его из массива $_POST. Внутри составного оператора if (Request::post('save')) {} определяются операторы, выполняющиеся, если контроллеру передан параметр с именем save ($_POST[‘save’]), что соответствует нажатой кнопке “Save”.

При этом значения атрибутов объекта $product заполняются параметрами из массива $_POST, что соответствует строке

$product->setProperties($_POST);

После этого происходит проверка, чтобы все обязательные поля формы были заполнены, а поле price имело числовое ненулевое значение.

$result = Product::getManager()->validate($_POST, Product::getClassName());

Менеджер объектов, получаемый методом Product::getManager(), проверяет, чтобы элементы массива $_POST, являющиеся атрибутами класса Product (id, title, description, price) удовлетворяли описанным в «карте» ограничениям.

В случае успешной проверки метод validate() менеджера объектов возвращает true, иначе массив ошибок, который потом помещается в массив errors.

Если проверка прошла успешно, то объект сохраняется в базе данных. Для этого используется метод save(). После чего пользователь пересылается вновь на страницу просмотра товаров, где видит сообщение “Product was saved” (Товар был сохранён). Для переадресации страницы используется метод Response::locate(), который посылает браузеру заголовок Location со значением, равным адресу страницы “listProducts”.

21

Аргумент метода locate – это либо строка URL, либо массив. Элементы массива задают контроллер, страницу и дополнительные параметры GET. В данном случае используется тот же контроллер, поэтому он не задан явно. Ключ action указывает на страницу просмотра товаров.

2.2.6.1 Флеш-параметры

Перед тем как перезагрузить страницу приложение устанавливает флеш-параметр message.

$this->setFlash('message', 'Product was saved');

Этот параметр сохраняется в сессии приложения (каждое приложение PHP on Rails имеет сессию по умолчанию). Время жизни флеш-параметра такое, что он доступен только при следующем показе страницы приложения. Отредактируйте товар и вы увидите это сообщение. Обновите данную страницу ещё раз. Сообщение пропало.

Для задания флеш-параметра используется метод Controller::setFlash(), в который передаются два аргумента: имя и значение параметра.

Для получения значения флеш-параметра используется метод Controller::getFlash(), который получает только один аргумент имя параметра. Если запрашивается несуществующий параметр, то возвращается NULL.

2.2.7 Улучшение интерфейса

На данный момент приложение depot выглядит очень непривлекательно. Так что настало время придать ему более презентабельный вид. Общая идея дизайна изображена на рисунке ниже.

изображена на рисунке ниже . Данный дизайн использует css-

Данный дизайн использует css-файл, который необходимо разместить по адресу public/css/style.css, поскольку он может (и будет) использоваться многими контроллерами.

public/css/style.css

/***** Page *****/

22

body {

 

background:#636958;

color:#333;

font:70% Verdana,Tahoma,Arial,sans-serif;

margin:0;

padding:0;

}

/**** Links ****/

A:link { text-decoration: underline; color: #003077; } A:visited { text-decoration: underline; color: #205090;} A:active { text-decoration: underline; color: #FF9900;} A:hover { text-decoration: none; color: #FF9900;}

/***** Wrapper *****/

#wrapper {

background:#f5f5f5;

border-left:5px solid #53584A; border-right:5px solid #53584A;

color:#000;

margin:0 auto;

padding:0;

width:550px;

}

/***** Top *****/

#top{

 

background:#69C;

color:#fff;

height:40px;

margin:0;

padding:0;

}

/***** Navigation *****/

#navigation ul,#navigation li {

margin:0;

padding:0;

}

#navigation {

background:#71A70B;

color:#fff;

font-size:1em;

height:2em;

line-height:2em;

}

#navigation li { float:left; list-style:none; white-space:nowrap;

}

#navigation li a { background:inherit; color:#fff; display:block; font-weight:bold;

23

padding:0 15px; text-decoration:none; text-transform:uppercase;

}

* html #navigation a {width:1%;}

#navigation .selected,#navigation a:hover {

background:#81C00C;

color:#fff;

text-decoration:none;

}

/***** Content *****/

#content { background:#fff; float:left; padding:15px 10px;

width:450px;

color:#000;

}

/***** Footer *****/

#footer {

background:#71A70B;

clear:both; color:#fff;

font-size:.9em;

height:1.8em;

line-height:1.8em;

padding:0;

text-align:center;

}

HTML код страницы можно разделить на три логические части: заголовок с меню, содержательную и нижнюю. Для всех страниц контроллера admin первая и последняя части одинаковы, поэтому их можно поместить в отдельные файлы: header.tpl и footer.tpl.

app/views/templates/admin/header.tpl

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"

"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> <head> <meta http-equiv="content-type" content="text/html; windows-1251" /> <link rel="stylesheet" type="text/css" href=" /css/style.css" media="screen" /> <title>Администрирование магазина</title> </head>

<body> <div id="wrapper">

<div id="top" align="center"> <h1>Администрирование магазина</h1> </div>

<div id="navigation"> <ul> <li>

24

{link controller="admin" action="listProducts" _class="selected"} Просмотр товаров {/link} </li> </ul> </div>

<div id="content">

app/views/templates/admin/footer.tpl

</div>

<div id="footer">&copy; 2006 Depot</span></div> </div> </body> </html>

Теперь остаётся только подключить эти шаблоны к уже созданным страницам. Для этого добавьте в начало файлов listProducts.tpl и editProduct.tpl строку

{include file="header.tpl"}

, а в конец

{include file="footer.tpl"}

Теперь посмотрите, как выглядит приложение Depot после изменения дизайна.

25

Старайтесь выделять общие фрагменты дизайна в отдельные файлы

Старайтесь выделять общие фрагменты дизайна в отдельные файлы, как это было сделано с header.tpl и footer.tpl, чтобы их легко можно было использовать повторно.

2.2.8 Удаление товара

Запрос на удаление товара обслуживается методом actionRemoveProduct() контроллера admin.

public function actionRemoveProduct() { $product = Product::load(Request::param('productId')); $product->remove(); $this->setFlash('message', 'Product was removed');

Response::locate2referer();

}

Он очень простой. Сначала загружается объект $product по первичному ключу, указанному в URL, а потом вызывается метод remove(), который удаляет объект из базы данных. После этого задаётся значение флеш-параметра message и браузеру посылается заголовок перейти на предыдущую страницу. Метод Response:: locate2referer() аналогичен вызову метода locate() с параметром, равным $_SERVER[‘HTTP_REFERER’].

26

Метод actionRemoveProduct не использует представление, и поэтому для него нет шаблона removeProduct.tpl.

2.2.9 Добавление нового атрибута

Как только администратор создаёт новый вид товара, используя разработанный выше интерфейс, то этот товар сразу становится доступен покупателю. Это не всегда то, что хочет продавец. Логично было бы указывать дату, начиная с которой товар поступает в продажу. Для этого необходимо добавить в таблицу products новое поле. Это легко сделать при помощи следующей SQL-команды.

ALTER TABLE products ADD COLUMN availableDate DATE

Чтобы приложение знало о новом поле в таблице products и умело с ним работать, нужно добавить в «карту» config/ormmapping/Product.hbm.xml строку

<property name="availableDate" column="availableDate" type="date"/>

, которая определяет атрибут типа date, а в класс Product - два новых метода.

public function setAvailableDate(DateTime $value) {

}

$this->

set('availableDate',

$value);

public function getAvailableDate() { return $this-> get('availableDate');

}

Для хранения атрибута availableDate используется класс DateTime.

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

2.2.9.1 Просмотр товаров

Ниже приведён фрагмент код шаблона app/views/templates/admin/listProducts.tpl. Красным цветом в нём показаны изменённые, а синим новые строки.

{include file="header.tpl"} ……… <tr align="center"> <TD bgcolor="#90C090" colspan="7"> <STRONG>Products</STRONG> ………

</tr>

</TD>

{if $message} <tr align="center"> <TD bgcolor="#FFFFFF" colspan="7"> ………

</tr>

{/if}

</TD>

<tr align="center">

27

……… <TD bgcolor="#90C090"><STRONG>Price</STRONG></TD> <TD bgcolor="#90C090"><STRONG>Available Date</STRONG></TD> ………

</tr>

{foreach from=$products item=product}

<tr valign="top"> ……… <TD bgcolor="#FFFFFF"> {$product->getPrice()|escape}

</TD> <TD bgcolor="#FFFFFF"> {if $product->isNull('availableDate')} не указана {else} {assign var="availableDate" value=$product->getAvailableDate()} {$availableDate->getTimestamp()|date_format:"%A, %B %e, %Y"} {/if} </TD> ………

</tr>

{foreachelse}

<tr>

<TD bgcolor="#FFFFFF" colspan="7" align="center"> No products </TD>

</tr>

{/foreach}

<tr>

<TD bgcolor="#FFFFFF" colspan="7" align="center"> {assign var="currentRange" value=$pagination->getCurrentRange()} {foreach from=$pagination->getRanges() item=range} ……… {/foreach} </TD>

</tr>

</table> {include file="footer.tpl"}

Если дата не указана, то вместо неё выводится соответствующее сообщение. В противном случае отображается отформатированная строка.

2.2.9.2 Добавление и редактирование товара

Ниже приведён фрагмент файла app/views/templates/admin/editProduct.tpl. Цветовые обозначения остаются те же.

{include file="header.tpl"} <FORM method="post"> <table border="0" cellpadding="4" cellspacing="1" align="center"

bgcolor="#90C090">

………

<TR>

<TD bgcolor="#90C090"><STRONG>Price</STRONG></TD> <TD bgcolor="#FFFFFF">

28

<INPUT type="text" name="price" value="{$product->getPrice()|escape}"/> </TD>

</TR>

<TR>

<TD bgcolor="#90C090"><STRONG>Available date</STRONG></TD> <TD bgcolor="#FFFFFF"> {if $product->isNull('availableDate')} {html_select_date field_array="availableDate"} {else} {assign var="availableDate" value=$product->getAvailableDate()} {html_select_date field_array="availableDate" time=$availableDate- >getTimestamp()} {/if} </TD>

</TR> ……… </table> </FORM> {include file="footer.tpl"}

{/if} </TD> </TR> ……… </table> </FORM> {include file="footer.tpl"} 29

29

2.3

Каталог товаров

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

Логично разместить каталог товаров на главной странице приложения - http://rails.loc/. Как уже упоминалось, все страницы, расположенные по этому URL обслуживаются контроллером root. Класс RootController изначально имеет метод actionIndex, который соответствует значению по умолчанию параметра action. Именно этот метод и будет реализовывать просмотр каталога товаров. Шаблон представления данной страницы находится в файле app/views/templates/root/index.tpl.

Контроллер должен передать представлению список товаров. Список товаров будет предоставлен моделью. Вот как выглядит метод actionIndex контроллера RootController.

public function actionIndex() {

/** * Display template */ $this->smarty->assign('products', Product::salableItems()); $this->smarty->display('index.tpl');

}

Этот