Часть 1 Введение
1.1 Введение
MZZ.Framework - это инструмент для людей, которые создают веб-приложения на PHP.
Основная цель MZZ - предоставить разработчикам набор средств для быстрой и гибкой разработки.
MZZ избавляет вас от необходимости рутинной и монотонной организации окружения и работы веб-приложения:
обработки сессий, авторизации и аутентификации пользователей, работы с данными, валидации форм...
Вы концентрируетесь лишь на реализации бизнес-логики приложения.
Краткий список особенностей MZZ:
- Код изначально написан под PHP5
- Smarty в качестве шаблонизатора, с рядом реализованных хелперов
- Встроенный ORM
- Генерация кода
- MVC-архитектура
- Генерация дружественных урлов (ЧПУ)
- Удобная расширяемость
- Встроенная система авторизации, основанная на политике ролей
- Локализация и интернационализация приложения
- Кеширование
- И многое другое...
1.2 Сообщество
Дополнительные ресурсы, где вы можете получить помощь по MZZ:
- IRC: канал #mzz на irc.rus.net (http://www.rus-net.org/)
- форум (todo)
О найденных ошибках вы можете сообщить в баг-трекер http://trac.mzz.ru/
Часть 2 Установка и настройка
2.1 Исходный код
Исходные коды MZZ могут быть получены с официального сайта из раздела загрузки.
Все версии, а также текущая нестабильная ветвь разработки могут быть получены из SVN-репозитория:
svn checkout svn://svn.mzz.ru/mzz/trunk
Имейте в виду, мы не можем точно гарантировать что код, взятый из репозитория, будет функционировать.
2.2 Системные требования
Минимальные системные требования к программному обеспечению веб-сервера:- PHP 5 >= 5.1.4
- PHP модули: PDO, PDO_MYSQL, PCRE, GD2
- MySQL: >4.1, 5(желательно)
- HTTP-сервер: Apache 1.x/2.x (с модулем mod_rewrite), IIS 6 и выше с наличием модуля rewrite (например, isapirewrite), nginx
- OS: Windows / UNIX
Перед первым запуском будет определена совместимость с ПО веб-сервера.
Если приложение совместимо, будет создан файл tmp/checked, блокирующий
проверку при следующих запусках. В случае несоответствия будут отображены причины.
2.3 Установка MZZ на сервер
Для установки MZZ на сервер необходимо скачать его и распаковать в любую директорию, доступную для чтения веб-сервером. Далее вашему приложению необходимо указать этот путь в качестве системной директории (константа SYSTEM_PATH, см.далее). Нет необходимости настраивать доступ к этой директории через веб (это необходимо только для запуска тестов и чтения поставляемой документации).
2.4 Установка и конфигурирование demo-приложения
2.4.1 Скачивание
Исходные коды demo-приложения могут быть получены из SVN-репозитория:
svn checkout svn://svn.mzz.ru/mzz/apps/demo/trunk
2.4.2 Установка
-
Настройте ваш веб-сервер таким образом, чтобы корень вебсайта указывал на директорию www demo-приложения. Пример конфигурационной директивы для apache:
DocumentRoot /home/user/demo/www
-
Установите для веб-сервера права, позволяющие осуществщять запись в директорию tmp demo-приложения.
chown apache:apache -R /home/user/demo/tmp chmod 755 -R /home/user/demo/tmp
-
Импортируйте тестовую базу
mysql -u user -ppassword < /home/user/demo/db/demo.sql
В процессе импорта удалятся существующие БД с именами "mzz". Для использования другого имени базы данных отредактируйте в /db/mzz.sql
-
Укажите в файле config.php абсолютный путь до системных библиотек MZZ. Например:
В приведённом примере MZZ располагается по адресу:
define('SYSTEM_PATH', realpath(dirname(__FILE__) . '/../MZZ/system/'));
/home/user/MZZ -
Измените в файле config.php параметры подключения к БД demo-приложения, если необходимо. Например:
systemConfig::$db['default']['dsn'] = 'mysql:host=localhost;dbname=mzz'; systemConfig::$db['default']['user'] = 'root'; systemConfig::$db['default']['password'] = '';
Готово! Теперь demo-приложение доступно по адресу, указанному в настройке виртуального хоста!
2.5 Установка и конфигурирование dummy-приложения
dummy-приложение - это макет нового "пустого" приложения, который можно использовать как основу для разработки новых приложений. В данном приложении не подключено ниодного модуля, кроме модуля-пустышки dummy.
Исходные коды dummy-приложения могут быть получены из SVN-репозитория:
svn checkout svn://svn.mzz.ru/mzz/apps/dummy/trunk
Установка dummy-приложения практически аналогична установке demo-приложения.
Часть 3 Структура
3.1 MZZ
Структура фреймворка:
- MZZ/
- docs/ — документация
- libs/ — сторонние библиотеки
- system/ — непосредственно код фреймворка
- cache/ — классы кеширования
- codegenerator/ — генераторы кода
- core/ — ядро
- db/ — классы для работы с БД
- exceptions/ — исключения
- filters/ — фильтры (todo: ссылка на описание процесса запуска приложения)
- forms/ — form-генераторы, валидаторы (todo: ссылка на генерацию и валидацию форм)
- i18n/ — локализация
- modules/ — системные и стандартные модули
- orm/ — классы ORM (подробнее)
- request/ — классы запросов, ответов и разбора роутов (todo: ссылка на генерацию и валидацию форм)
- resolver/ — резолверы (todo: ссылка на разделы описания резолверов и их фич)
- routes/ — роуты (todo: ссылка на разделы описания роутеров)
- service/ — различные сервисные классы
- session/ — классы для работы с сессиями
- template/ — классы шаблонизатора
- toolkit/ — toolkit (подробнее)
- tests/ — модульные тесты
3.2 Приложение
3.2.1 Обзор
Структура типичного приложения:
- MZZ_app/
- db/ — дампы базы данных приложения
- files/ — файлы, хранимые модулем
fileManager - modules/ — модули приложения
- routes/ — роуты приложения
- templates/ — шаблоны
- tmp/ — временные файлы
- www/ — корневая директория приложения
- css/ — файлы стилей
- images/ — файлы изображений
- js/ — файлы javascript
- index.php — точка входа в приложение, именно этому файлу передаётся управление
- application.php — класс приложения
- config.php — конфигурационный файл
3.2.2 Конфигурационный файл
Типичный конфигурационный файл (config.php):
<?php /** * Абсолютный путь до сайта. * Если mzz установлен в корень веб-сервера, оставьте поле пустым * Правильно: /mzz, /new/site * Неправильно: site1, site1/, /site1/ */ define('SITE_PATH', ''); define('COOKIE_DOMAIN', ''); define('DEBUG_MODE', true); // Путь до директории system в mzz define('SYSTEM_PATH', realpath(dirname(__FILE__) . '/../system/')); // Идентификатор записи в БД для неавторизированных пользователей define('MZZ_USER_GUEST_ID', 1); // Идентификатор группы, для которой ACL всегда будет возвращать true (т.е. предоставит полный доступ) define('MZZ_ROOT_GID', 3); require_once SYSTEM_PATH . DIRECTORY_SEPARATOR . 'systemConfig.php'; // Дефолтный язык приложения systemConfig::$i18n = 'ru'; // Включаем мультиязычность systemConfig::$i18nEnable = true; // Устанавливаем дефолтную кодировку для выдачи ini_set('default_charset', 'utf-8'); // Настройка соединения с БД приложения systemConfig::$db['default']['driver'] = 'pdo'; systemConfig::$db['default']['dsn'] = 'mysql:host=localhost;dbname=mzz'; systemConfig::$db['default']['user'] = 'root'; systemConfig::$db['default']['password'] = ''; systemConfig::$db['default']['options'] = array(); systemConfig::$db['default']['options']['init_query'] = 'SET NAMES utf8'; // Установка переменных окружения systemConfig::$appName = 'demo'; systemConfig::$appVersion = '1.0-alpha'; systemConfig::$enabledModules = array('captcha', 'comments', 'fileManager', 'menu', 'news', 'page'); systemConfig::$pathToApplication = dirname(__FILE__); systemConfig::$pathToWebRoot = systemConfig::$pathToApplication . '/www'; systemConfig::$pathToTemp = systemConfig::$pathToApplication . '/tmp'; systemConfig::$pathToConfigs = systemConfig::$pathToApplication . '/configs'; // Настройка дефолтного мейлера systemConfig::$mailer['default']['backend'] = 'PHPMailer'; systemConfig::$mailer['default']['params'] = array('html' => true, 'smtp' => true, 'smtp_host' => 'localhost'); /** * Здесь могут находиться другие системные настройки приложения, * такие как настройки кеширования, шаблонизатора, сессий и др. */ systemConfig::init(); ?>
Опишем параметры конфигурационного файла:
| Директива | Описание |
|---|---|
define('DEBUG_MODE', ...); |
Режим отладки. |
define('SYSTEM_PATH', ...)); |
Абсолютный путь до system каталога MZZ. |
systemConfig::$appName systemConfig::$appVersion |
Имя и версия вашего приложения. |
systemConfig::$pathToApplication |
Путь до каталога вашего приложения. |
systemConfig::$pathToWebRoot |
Путь до web-root вашего приложения. |
systemConfig::$pathToTemp |
Путь до tmp каталога приложения. |
systemConfig::$pathToConf |
Путь до каталога с конфигурационными файлами приложения. |
3.2.3 Класс приложения
Типичный класс приложения (application.php):
<?php class application extends core { } ?>
Этот класс ответственен за запуск приложения и наследуется от базового класса приложения core.
В этом классе могут быть переопределены методы, ответственные за запуск приложения,
компановку фильтров,
сборку композитного тулкита,
загрузку базовых классов
и проверку системы на совместимость с MZZ и подключенными модулями.
В обычных случаях не требуется никакого вмешательства в код этого класса.
3.3 Модуль
3.3.1 Обзор
Типичная структура модуля в приложении MZZ выглядит следующим образом:
- news/
- actions/
- controllers/
- i18n/
- mappers/
- models/
- templates/
- newsModule.php
3.3.2 Класс модуля
newsModule.php представляет собой класс, описывающий модуль.
class newsModule extends simpleModule { protected $classes = array( 'news', 'newsFolder'); protected $roles = array( 'moderator', 'user'); public function getRoutes() { return array( array(), array( 'newsFolder' => new requestRoute('news/:name/:action', array( 'module' => 'news', 'name' => 'root', 'action' => 'list'), array( 'name' => '.*?', 'action' => '(?:list|create|createFolder|editFolder|deleteFolder|moveFolder)')))); } }
Данный класс должен наследоваться от класса simpleModule и служит для хранения служебной информации о модуле. Так, к примеру, в данном классе содержится информация:
- о классах модуля (массив protected $classes)
- роли, используемые в модуле (todo: ссылка на соответствующий раздел)
- модуле-зависимые роуты (возвращается массив из двух элементов, в котором первый массив содержит роуты, которые должны быть добавлены в начало и второй массив - в конец)
- иконка модуля в admin-интерфейсе фреймворка
Помимо всего прочего, данный класс отвечает за получение мапперов и действий модуля, что позволит, к примеру, реализовать нестандартную схему получения маппера для объекта или создать так называемые multi-action контроллеры (todo: сделать раздел доки tricks и описать там такой финт)
3.3.3 Actions
В директории actions расположены файлы, определяющие набор возможных действий с каждой из сущностей модуля. Для модуля news это будут файлы news.php и newsFolder.php:
news.php:
<?php return array( 'view' => array( 'controller' => 'view' ), 'edit' => array( 'controller' => 'save', 'jip' => 1, 'role' => array('moderator'), 'icon' => 'sprite:mzz-icon/page-text/edit', 'lang' => true, 'main' => 'active.blank.tpl' ), 'move' => array( 'controller' => 'move', 'jip' => 1, 'role' => array('moderator'), 'icon' => 'sprite:mzz-icon/page-text/move' ), 'delete' => array( 'controller' => 'delete', 'jip' => 1, 'role' => array('moderator'), 'icon' => 'sprite:mzz-icon/page-text/del', 'confirm' => '_ news/confirm_delete', 'main' => 'active.blank.tpl' ), 'admin' => array( 'role' => array('moderator'), 'controller' => 'admin', 'title' => '_ admin', 'admin' => true ) ); ?>
В этом файле определены 5 действий: view, edit, move, delete и admin. Назначение этих действий легко определяется по их названию.
Рассмотрим пример простейшего actions-конфига:
<?php return array( 'view' => array( 'controller' => 'view', ) ); ?>
Как видно из примера, действие имеет только один обязательный параметр — controller. Представляет собой имя контроллера, который будет обслуживать данное действие.
Также, доступны следующие необязательные параметры для действий:
| Имя переменной | Описание | Принимаемые значения |
|---|---|---|
| main |
Main-шаблон для действия (todo: где описать для чего это? в процессе запуска приложения или прям тут?). Может принимать следующие значения:
Если данный параметр не задан, то по-умолчанию будет использоваться active.main.tpl шаблон. |
string |
| jip |
Является ли действие действием JIP (todo: ссылка на описание JIP).
Чтобы у модели было JIP-меню, в маппере необходимо подключить плагин jip.
|
int |
| jip_target | Указывает для JIP будет ли действие открываться в JIP-окне (по умолчанию) или будет открываться в новом окне/вкладке (значение 'new'). |
string |
| icon | Иконка действия (используется для JIP) (todo: ссылка на описание). | string |
| confirm | Сообщение перед выполнением действия (используется для JIP). | string |
| title | Заголовок действия (используется для JIP). Если строка начинается со знака "_", то значение возмется из i18n. | string |
| admin |
Является ли действие действием admin-части фреймворка. Если флаг выставлен в true, то данное действие будет отображаться
в административной части приложения (/admin), где дополнительно появится пункт в меню с названием этого действия,
параметр main будет выставлен в 'active.admin.tpl' (если не был выставлен в какое-то значение)
|
bool |
| role | Массив или строка ролей модуля, которым будем разрешен доступ к этому действию. Если данный параметр не указан, то доступ к действию разрешен всем. (todo: ссылка на описание механизма ролей) | array|string |
| route_name | Имя роута, который будет использоваться для построения URL'а этого действия, если используется JIP. | string |
| route.myparam |
Значение, которое будет подставлено в параметр myparam роута route_name (если используется).
В действии можно описать несколько параметров вида route.myparam для всех необходимых параметров роута.
В качестве значения этого параметра можно использовать имена методов модели, к которой относится данный экшн, в виде
'->getId' или '->getUser->getId'. Также можно использовать и скаляры.
|
string |
3.3.4 Controllers
Контроллеры предназначены для обеспечения взаимодействия моделей и отображения.
Подробнее о контроллерах читайте в соответствующем разделе.
3.3.5 i18n
В этой директории хранятся файлы локализации модуля (если она требуется). Файлы создаются отдельно для каждого из языков. Причём файлы должны именоваться следующим образом: <имя_языка>.ini. Например: en.ini.
todo ссылка на главу о i18n
3.3.6 Mappers и models
В каталоге mappers/ модуля хранятся мапперы классов, в каталоге models/ — соответствующие модели. Вместе эти классы являются частью компоненты Model
в парадигме MVC.
Подробнее про Model и MVC читайте в соответствующем разделе.
3.3.7 Templates
В этой директории хранятся smarty шаблоны
3.4 Процесс запуска приложения
3.4.1 Обзор
Для разработки и отладки своих приложений необходимо четкое понимание того, как происходит обработка входящих запросов, как работает фреймворк, в какой последовательности выполняется код внутри приложения.
3.4.2 Порядок выполнения кода
- Точкой входа для приложения является index.php, который располагается в корне web-директории приложения (папке /www). В этом файле подключается основной файл конфигурации приложения config.php, базовые классы приложения (в system/index.php), основной класс приложения application.php. В классе application могут быть переопределен процесс запуска приложения, если это необходимо (бывает редко), также в нем можно скомпоновать toolkit и подгрузить какие-то свои базовые классы.
- Далее приложение стартует путем вызова $application->run().
- В процессе запуска происходит компановка резолвера (метод composeResolvers) (todo: ссылка на описание резолвера), загрузка необходимых для старта файлов (метод loadCommonFiles), сборка композитного тулкита (метод composeToolkit), проверка приложения и его компонентов (метод check), затем управление переходит на метод handle.
-
В методе handle происходит сборка цепи фильтров (метод composeFilters),
которые должны обработать входящий запрос. Обычный набор фильтров состоит из следующей последовательности:
- timingFilter — фильтр для подсчета времени выполнения запроса
- sessionFilter — фильтр предназначен для подготовки и старта сессии
- routingFilter — фильтр, в котором происходит процесс роутинга запроса
- userFilter — фильтр определяет и устанавливает текущего пользователя, который выполняет запрос
- userPreferencesFilter — фильтр устанавливает текущие настройки пользователя для приложения (локаль, часовой пояс, скин...)
- contentFilter — фильтр получения и отображения контента
3.4.3 Маршрутизация запросов (Routing)
Часть 4 MVC
4.1 Model
Компонент Model парадигмы MVC в MZZ представлен собственной реализацией паттерна проектирования The Data Mapper Pattern. Этот паттерн подразумевает два набора классов: мапперы - классы, которые хранят метаописание сущностей, отношения между сущностями, выполняют работу по извлечению из БД и сохранению данных в БД, итд; объекты - контейнеры, которые хранят предоставляют интерфейс для доступа и модификации данных. Более подробно о работе с моделями можно познакомиться в главе ORM (todo ссылка).
4.2 View
4.2.1 Smarty
В MZZ для реализации компонента View используется библиотека Smarty 2.6.26 с небольшими модификациями.
Доступ к smarty есть в каждом контроллере приложения, посредством свойства smarty (подробнее). Пример работы со smarty в контроллере:
$this->smarty->assign('foo', 'bar');
Также, если требуется получить объект smarty не в контроллере, его можно получить из toolkit'а: (todo ссылка)
$toolkit = systemToolkit::getInstance(); $smarty = $toolkit->getSmarty();
4.2.2 Функции
Функция {load} предназначена для запуска модулей из шаблонов. MZZ предоставляет возможность запускать в шаблонах любые действия модулей, также предоставляя средства для организации взаимодействия модулей.
Синтаксис:
{load module="" action="" <переменная>="значение" ...}
Пример: запуск действия list модуля news:
{load module="news" action="list"}
4.3 Controller
4.3.1 Создание
Контроллеры располагаются в директории controllers модуля.
Имена контроллеров должны соответствовать следующему соглашению: <Имя_модуля><Имя_контроллера>Controller (todo: описать, откуда берется <Имя_контроллера>, а то не совсем ясно что это за имя).
Например, для модуля news контроллер view будет называться: newsViewController.
Контроллеры в ваших модулях должны наследоваться от базового абстрактного класса simpleController. Каждый контроллер должен реализовывать защищённый (protected) метод getView().
Таким образом, простейшим примером может выступать следующий контроллер, возвращающий клиенту строку Hello, world!:
class helloWorldController extends simpleController { protected function getView() { return 'Hello, world!'; } }
В большинстве случаев вам не придется вручную создавать файлы и базовый код ваших контроллеров. Все эти действия выполнит встроенный в фреймворк инструмент скаффолдинга devToolbar (todo ссылка на тулбар)
4.3.2 Базовые методы и свойства
Для упрощения процесса разработки базовый класс simpleController предоставляет ряд методов и свойств.
Методы:
| Метод | Аргументы | Описание |
|---|---|---|
| setPager |
|
Присоединяет класс пагинации к мапперу, который выбирает данные на страницу. Также в smarty автоматически передаётся инстанция объекта-пейджера с именем $pager (todo ссылка на описание класса), который можно отобразить в произвольном месте.Пример использования: Выберем список новостей, по 10 новостей на странице. И отобразим их в обратном порядке: newsListController.php:
$this->setPager($newsFolderMapper, 10, true); // устанавливаем pager $this->smarty->assign('news', $newsFolderMapper->getItems($newsFolder)); // ищем новости list.tpl:
<div class="pages">{$pager->toString()}</div> |
| fetch |
|
Запуск на исполнение указанного шаблона.
return $this->fetch('news/list.tpl'); |
| redirect |
|
Перенаправление пользователя по указанному урлу. Пример использования: Перенеправление на главную страницу приложения: $url = new url('default'); return $this->redirect($url->get()); |
| forward |
|
Передача управления другому контроллеру. Пример использования: Вызов из текущего контроллера действия dashboard модуля admin:
return $this->forward('admin', 'dashboard'); |
Свойства:
| Свойство | Тип | Описание |
|---|---|---|
| $toolkit | stdToolkit |
Объект toolkit (todo ссылка на описание)
$id = $this->toolkit->getMapper('news', 'news'); |
| $request | httpRequest | Объект запроса (todo ссылка на описание)
$path = $this->request->getString('path'); |
| $response | httpResponse | Объект ответа (todo ссылка на описание)
$this->response->setCookie('cookie_name', 'cookie_data'); |
| $smarty | mzzSmarty | Smarty (подробнее)
$this->smarty->assign('news', $newsFolderMapper->getItems($newsFolder)); $this->smarty->assign('folderPath', $newsFolder->getTreePath()); |
4.3.3 Служебные
-
simple404Controller. Предназначен для вывода стандартной ошибки 404 "Запрашиваемый объект не найден".
Если вам нужно отобразить 404 ошибку у себя в контроллере - следует использовать примерно такой код:Также существует альтернативный способ передачи управления контроллеру 404 ошибки (todo ссылка на описание forward404):$controller = new simple404Controller(); return $controller->run();
В этом случае будет произведён поиск контроллера$mapper = $this->toolkit->getMapper('news', 'news'); ... $this->forward404($mapper);
news404Controllerв директорииnews/controllers. И в случае, если он будет найден - то будет вызван именно он. В противном случае - также будет вызыватьсяsimple404Controller.
В качестве шаблона для этого контроллера используется файлsimple/404.tpl, который вы, конечно же, можете переопределить у себя в проекте (подробнее)
Также он автоматически вызывается для адресов, которые не были обработаны ни одним из роутов. -
simple403Controller. Предназначен для вывода стандартной ошибки 404 "Доступ запрещён".
Если вам нужно отобразить 403 ошибку у себя в контроллере - следует использовать примерно такой код:Также существует альтернативный способ передачи управления контроллеру 403 ошибки (todo ссылка на описание forward403):$controller = new simple403Controller(); return $controller->run();
В этом случае будет произведён поиск контроллера$mapper = $this->toolkit->getMapper('news', 'news'); ... $this->forward403($mapper);
news403Controllerв директорииnews/controllers. И в случае, если он будет найден - то будет вызван именно он. В противном случае - также будет вызыватьсяsimple403Controller.
В качестве шаблона для этого контроллера используется файлsimple/403.tpl, который вы, конечно же, можете переопределить у себя в проекте (подробнее)
Также он автоматически вызывается для адресов, для которых в результате проверки прав достпупа был получен отказ (todo ссылка на роли).
Часть 5 ORM
5.1 Обзор
ORM в mzz предназначен для упрощения работы с данными в БД. Стандартные классы, входящие в ORM обладают минимально необходимым функционалом - CRUD (Create, Retrieve, Update, Delete).
ORM построен на базе The Data Mapper Pattern (рекомендуем ознакомиться со статьей перед продолжением чтения документации). Вкратце: в контектсте этого паттерна мы оперируем двумя терминами - маппер и доменный объект (ДО). Упрощённо: доменный объект - контейнер для данных, маппер - класс для заполнения ДО данными. Все действия по модификации также осуществляются через маппер.
Также отметим, что ДО является отображением данных приложения и данных в БД (в пределах 1 сеанса). Из этого следует, что до тех пор, пока объект не был сохранён специальным методом маппера, он будет выдавать "старые" данные (именно те, которые сейчас находятся в БД). Проиллюстрирую это на примере:
<?php // $news - Доменный Объект // $newsMapper - его маппер $news = $newsMapper->searchById(1); echo $news->getId(); // 1 echo $news->getTitle(); // "Заголовок для новости 1" $news->setTitle('Новый заголовок'); echo $news->getTitle(); // "Заголовок для новости 1" $newsMapper->save($news); echo $news->getTitle(); // "Новый заголовок" ?>
DO лежат в подкаталоге models каждого модуля, мапперы - в подкаталоге mappers.
5.2 Мапперы
Мапперы это активная составляющая реализации паттерна The Data Mapper pattern. Именно мапперы производят отображение записей базы данных на объекты приложения и обратно, организуют связи между объектами разных типов, различными средствами автоматизируют рутинные задачи.
Пример типичного маппера:
<?php /** * $URL: svn://svn.mzz.ru/mzz/trunk/system/modules/news/mappers/newsMapper.php $ * * MZZ Content Management System (c) 2005-2007 * Website : http://www.mzz.ru * * This program is free software and released under * the GNU/GPL License (See /docs/GPL.txt). * * @link http://www.mzz.ru * @version $Id: newsMapper.php 4267 2010-07-09 14:34:11Z desperado $ */ fileLoader::load('news/models/news'); fileLoader::load('modules/comments/plugins/commentsPlugin'); fileLoader::load('modules/jip/plugins/jipPlugin'); fileLoader::load('modules/i18n/plugins/i18nPlugin'); /** * newsMapper: маппер для новостей * * @package modules * @subpackage news * @version 0.3 */ class newsMapper extends mapper { /** * Имя класса DataObject * * @var string */ protected $class = 'news'; protected $table = 'news_news'; protected $map = array( 'id' => array( 'accessor' => 'getId', 'mutator' => 'setId', 'options' => array( 'pk', 'once', ), ), 'folder_id' => array( 'accessor' => 'getFolder', 'mutator' => 'setFolder', 'relation' => 'one', 'foreign_key' => 'id', 'mapper' => 'news/newsFolder' ), 'title' => array( 'accessor' => 'getTitle', 'mutator' => 'setTitle', 'options' => array( 'i18n', ), ), 'editor' => array( 'accessor' => 'getEditor', 'mutator' => 'setEditor', 'relation' => 'one', 'foreign_key' => 'id', 'mapper' => 'user/user' ), 'annotation' => array( 'accessor' => 'getAnnotation', 'mutator' => 'setAnnotation', 'options' => array( 'i18n', ), ), 'text' => array( 'accessor' => 'getText', 'mutator' => 'setText', 'options' => array( 'i18n', ), ), 'created' => array( 'accessor' => 'getCreated', 'mutator' => 'setCreated', 'options' => array( 'once', ), 'orderBy' => 1, 'orderByDirection' => 'DESC', ), 'updated' => array( 'accessor' => 'getUpdated', 'mutator' => 'setUpdated', ), ); public function __construct($module) { parent::__construct($module); $this->plugins('jip'); $this->plugins('i18n'); $this->plugins('comments'); } protected function preInsert(& $data) { if (is_array($data)) { $data['updated'] = $data['created']; } } protected function preUpdate(& $data) { if (is_array($data)) { $data['updated'] = new sqlFunction('UNIX_TIMESTAMP'); } } /** * Выполняет поиск объектов по идентификатору каталога * * @param integer $id идентификатор папки * @return array */ public function searchByFolder($folder_id) { return $this->searchAllByField('folder_id', $folder_id); } } ?>
В общем случае каждый маппер должен лишь переопределить 2 свойства:
- $table — имя таблицы БД
- $map — массив, представляющий собой правила наложения данных БД на объектную модель. (ссылка на подраздел Maps в этом же разделе)
Следует также обратить внимание на переменную $class. Эта переменная является опциональной и представляет собой имя класса, с которым работает данный маппер (в нашем случае это news). Если эта переменная не задана явно, то за имя класса берется имя таблицы.
5.3 Схема объекта
Свойство маппера $map представляет собой набор правил наложения данных БД на объектную модель. Вот несколько упрощенный пример map:
protected $map = array( 'id' => array( 'accessor' => 'getId', 'mutator' => 'setId', 'options' => array( 'pk', 'once', ) ), 'title' => array( 'accessor' => 'getTitle', 'mutator' => 'setTitle' ) )
Ключами массива $map являются имена полей таблицы БД (которая задается в параметре $table маппера, ссылка). В общем случае у каждого поля должно быть описано имена двух методов — accessor и mutator.
Имя accessor'а имеет префикс "get" и используется для получения данных, которые хранятся в данном поле.
Mutator, соответственно, имеет префикс "set" и используется для сохранения данных в DO.
У каждого поля может содержаться любое количество дополнительных параметров в ключе 'options'. Приведем примеры нескольких опций:
- pk — присутствие такой опции будет говорить о том, что данное поле является Первичным Ключом (Primary Key) для таблицы.
- once — поля с этой опцией будут доступны для сохранения только один раз при создании объекта. При попытке задать такое поле вторично, будет сгенерировано исключение
- ro — поле доступно только для чтения. При попытке задать значения через соответствующий мутатор, будет сгенерировано исключение.
Основные методы для работы с мапперами:
- create() - создание объекта.
- save($object) - сохранение объекта $object.
- delete($id) - удаление объекта, в качестве идентификатора используется первичный ключ $id или сам объект.
Также для удобства имеется ряд методов для получения записей:
- searchByKeys($id) - поиск объектов по первичным ключам. В качестве аргумента передаётся массив.
- searchOneByField($field, $value) - поиск объекта по полю $field со значением $value.
- searchAllByField($field, $value) - поиск объектов по полю $field со значение $value.
- searchOneByCriteria(criteria $criteria) - поиск объекта по критерию.
- searchAllByCriteria(criteria $criteria) - поиск объектов по критерию.
- searchAll($orderCriteria = null) - выборка всех объектов. В качестве аргумента может быть передан критерий для сортировки.
Естественно, что в ваших мапперах вы можете расширить данный список методов для поиска. Так например метод searchByLogin (метод для поиска пользователя по его логину) будет выглядеть следующим образом:
class userMapper extends simpleMapper { [...] /** * Выполняет поиск объекта по логину * * @param string $login логин * @return object */ public function searchByLogin($login) { return $this->searchOneByField('login', $login); } }
Экранировать значения, передаваемые в аргументах, не нужно. За вас это сделает генератор запросов.
О работе с критериями можно узнать в соответствующем разделе.
5.4 Хуки
В ORM mzz на основе паттерна The Observer реализована система хуков. Каждый из хуков вызывается после (или до) определённого действия в маппере. Также в хуки передаются данные, собственно с которыми код хука и должен работать. Полный список хуков может быть уточнён вами в файле orm/observer.php. Пример работы маппера с хуками:
<>
class newsMapper extends mapper
{
/**
* Имя класса DataObject
*
* @var string
*/
protected $class = 'news';
protected $table = 'news_news';
protected $map = array(...);
[...]
protected function preInsert(& $data)
{
if (is_array($data)) {
$data['updated'] = $data['created'];
}
}
protected function preUpdate(& $data)
{
if (is_array($data)) {
$data['updated'] = new sqlFunction('UNIX_TIMESTAMP');
}
}
[...]
}
<
В этом коде продемонстрировано, как автоматически можно менять время изменения и создания публикации во время изменения и создания нового объекта новости соответственно.
5.5 Плагины
Плагины предназначены для расширения функциональности мапперов без непосредственно их модификации. Функционал плагинов базируется на системе событий (на которой также построены хуки). Устройство плагинов рассмотрим на примере:
class obj_idPlugin extends observer { protected $options = array( 'obj_id_field' => 'obj_id' ); protected function updateMap(& $map) { $map[$this->options['obj_id_field']] = array( 'accessor' => 'getObjId', 'mutator' => 'setObjId', 'options' => array('once', 'plugin') ); } public function postCreate(entity $object) { if (!$object->getObjId()) { $obj_id = systemToolkit::getInstance()->getObjectId(); $map = $this->mapper->map(); $object->{$map[$this->options['obj_id_field']]['mutator']}($obj_id); $this->mapper->save($object); } } public function preInsert(array & $data) { $data[$this->options['obj_id_field']] = systemToolkit::getInstance()->getObjectId(); } public function setObjId(entity $object, $id) { $object->merge(array($this->options['obj_id_field'] => $id)); return $object; } public function getObjIdField() { return $this->options['obj_id_field']; } }
Этот плагин добавляет в маппер возможность работать с полем obj_id (оно активно используется в задачах, когда нужно уникально в пределах приложения определить объект). В этом плагине определено свойство $options, в котором хранится имя поля, в котором будет собственно сам уникальный счётчик. Метод updateMap будет автоматически вызван классом-предком observer и в качестве аргумента будет передана схема объекта, в которую, как видно из кода, будет добавлен новый метод.
Событие postCreate вызывается сразу после создания объекта. В обработчике проверяется, есть ли уже какое-то значение obj_id для данного объекта. Если нет - генерируется новое значение, передаётся объекту, после чего объект сохраняется. Таким образом это событие используется в случае, когда плагин подключен для уже имеющегося набора данных, для которого ещё не определены obj_id. Событие preInsert предназначено для объектов, которые только создаются - в этом случае уникальный номер генерируется и сразу передаётся в массив данных объекта.
Подключение плагинов происходит с помощью двух методов маппера:
mapper::plugins($name, array $options = array(), $newName = null);
или
mapper::attach(observer $observer, $name = null)
Общесистемные плагины располагаются в директории system/orm/plugins. Местоположение плагинов модулей жёстко не декларируется, но предпочтительно их размещать в поддиректории plugins директории с модулями.
В настоящее время вместе с mzz поставляются следующие плагины:
identityMapobj_id— плагин, позволяющий добавить в объекты поле obj_id для уникальной идентификации в приложении;tree_mp— плагин для работы с древовидными структурами. Иерархическая структура хранится в виде Materialized Path;tree_al— плагин для работы с древовидными структурами. Иерархическая структура хранится в виде Adjacency List;pager— плагин для постраничного вывода коллекций объектов. Вручную подключать его в маппер вам вряд ли понадобиться. Ознакомиться с этим плагином Вы можете в соответствующем разделе документации.
Часть 6 Шаблонизатор
6.1 Обзор
sdsdf6.2 Помощники видов или хелперы
Часто бывает так, что в скриптах вида необходимо повторно выполнять определенный набор функций; например, формирование даты, генерация элементов формы, отображение ссылок. Вы можете использовать помощников для выполнения этих действий.
Часть 7 Внешние данные и валидация форм
7.1 Обзор
7.2 Валидаторы
Часть 8 ACL и система прав
8.1 Обзор
Часть 9 Локализация модулей и приложений
9.1 Обзор
Часть 10 Стандартные пакеты
10.1 Toolkit
Тулкит предназначен для того, чтобы получать необходимыe для работы объекты. Тулкит является реализацией The Composite Pattern и The Registry Pattern. В стандартной поставке toolkit составляется из класса stdToolkit и вы можете посмотреть методы этого класса непосредственно в файле system/toolkit/stdToolkit.php. Сам объект toolkit'а вы можете получить с помощью метода-синглтона:
$toolkit = systemToolkit::getInstance();
Также в контроллерах toolkit уже доступен через свойство toolkit. Пример получения текущего пользователя:
$this->toolkit->getUser();
10.2 Cache
10.2.1 Обзор
Пакет cache предназначен для кеширования данных.
10.2.2 Конфигурирование
Работа с пакетом начинается с процесса его конфигурирования. В конфигурационном файле приложения вы можете определить новые конфигурации для cache, которые будут определять тип хранилища, время жизни данных и специфические для хранилищ свойства (путь хранения, префикс, итд).
Существует набор конфигураций по умолчанию:
| Название | Хранилище | Опции | Описание |
|---|---|---|---|
| fast | file |
'params' => array( 'path' => systemConfig::$pathToTemp . DIRECTORY_SEPARATOR . 'cache', 'prefix' => 'fast_', 'expire' => 180) |
Предназначен для хранения часто изменяющихся данных, которые по истечении очень короткого срока времени (180 секунд по умолчанию) должны будут устареть и заменены на свежие данные. Данные хранятся в поддиректории cache, временной директории проекта.
|
| session | session |
'params' => array('expire' => 1800) |
Предназначен для хранения данных, имеющих свою актуальность только в пределах пользовательской сессии. Стоит помнить, что данные, в этом случае, будут независимы для каждой из сессий и из одной сессии нельзя будет получить данные другой. |
| long | file |
'params' => array( 'path' => systemConfig::$pathToTemp . DIRECTORY_SEPARATOR . 'cache', 'prefix' => 'long_', 'expire' => 86400) |
Конфигурация аналогичная конфигурации fast, но предназначены наоборот - для хранения редко изменяющихся данных. Время хранения по умолчанию - сутки.
|
| memory | memory | Предназначен для хранения данных, имеющих свою актуальность только в пределах вызова скрипта. Данные хранятся в оперативной памяти и уничтожаются после завершения выполнения скрипта. | |
| default |
Конфигурация, которая будет использована в случае, если при получении объекта кэша не была указана любая из существующих конфигураций. По умолчанию является синонимом на конфигурацию fast.
|
В дополнение к этим конфигурациям вы можете добавить в своём приложении свои. Для этого в массив systemConfig::$cache просто необходимо добавить описание новой конфигурации. Ключ массива с конфигурацией - будет служить её именем.
Например, для добавления конфигурации кэша с именем myMemcacheConfig, которая бы хранила данные в memcache-хранилище нужно добавить следующие строки:
systemConfig::$cache['myMemcacheConfig'] = array( 'backend' => 'memcache' );
Если вам нужно изменить параметры стандартных конфигураций, то просто переопределите соответствующие ключи массива systemConfig::$cache.
Например, чтобы кэш fast начал работать с memcache вместо файлов, в конфигурационном файле нужно прописать:
systemConfig::$cache['fast'] = array( 'backend' => 'memcache' );
10.2.3 Использование
Работа с пакетом cache происходит единообразно для всех типов хранилищ и всех конфигураций. Для начала работы необходимо получить объект с нужной конфигурацией из toolkit'а:
$cache = $this->toolkit->getCache('fast');
В результате будет возвращён объект типа cache, реализующий интерфейс:
| Метод | Аргументы | Возвращаемое значение | Описание |
|---|---|---|---|
| get |
|
Boolean:
|
Этот метод используется для получения значений, сохранённых в кэше.
if ($cache->get('key', $value)) { echo 'Найденное значение: ' . $value; } else { echo 'Значение не найдено'; } |
| set |
|
Mixed: возвращаемое значение зависит от используемого хранилища. Обычно - true/false.
|
Этот метод используется для сохранения значений в кэше.
$data = array(1, 'some string'); $cache->set('key', $data); $data = 'some data to cache'; $cache->set('key', $data, array('sample', 'documentation'), 60); |
| delete |
|
Mixed: возвращаемое значение зависит от используемого хранилища. Обычно - true/false.
|
Этот метод используется для удаления значений из кэша.
$cache->delete('key'); |
| clear |
|
Mixed: возвращаемое значение зависит от используемого хранилища. Обычно - true/false.
|
Этот метод используется для удаления значений из кэша по связанному с ним тегу. Если ключей, ассоциированных с указанным тегом, несколько, тогда будут удалены все вхождения.
$data = 'some data to cache'; $cache->set('key', $data, array('sample', 'documentation'), 60); $cache->clear('sample'); |
| flush | void |
Этот метод используется для очистки содержимого всего хранилища.
$cache->flush(); В зависимости от типа хранилища, могут очищаться все данные в нём (как в memcache) или только относящиеся к данной конфигурации (как в file). |
10.3 Db
10.3.1 Объект соединения с БД
10.3.2 Генератор запросов
Для генерации SQL-запросов в MZZ предназначен специальный класс simpleSelect. SimpleSelect собирает запрос из составных частей, которыми являются объекты классов criteria. Интерфейс у simpleSelect предельно простой: конструктор, принимающий объект класса criteria и метод simpleSelect::toString(), возвращающий сгенерированный запрос.
Рассмотрим пример генерации простейшего запроса:
$criteria = new criteria('table'); $select = new simpleSelect($criteria); echo $select->toString(); // выведет "SELECT * FROM `table`"
Как видите - генератор запросов сам позаботился о помещении имени таблицы в обратные кавычки (`). Аналогичным образом вам не нужно заботиться об одинарных кавычках ('), в которые помещаются строковые константы, и об экранировании этих строковых констант.
10.3.3 Criteria
Класс criteria служит для указания параметров выборки: полей, условий, объединений таблиц, группировки, итд. Сам по себе этот класс не используется нигде в приложениях, но он необходим как набор правил для генерации запроса с помощью класса simpleSelect.
Так как каждый из методов criteria возвращает ссылку на сам объект критерии - то вызовы методов можно выстраивать в цепочку для уменьшения объёма кода и улучшения читабельности. Это будет рассмотрено в примерах ниже.
Интерфейс criteria:
.1 __construct
Указывает таблицу, для которой генерируется запрос.
__construct([string $table = null [,string $alias = null]])
$table |
имя таблицы |
$alias |
алиас |
.2 select
Указывает выбираемые поля
criteria select(string $field [, string $alias = null])
$field |
имя поля |
$alias |
алиас |
$criteria = new criteria('table'); $criteria->select('field1'); $criteria->select('field2', 'alias'); $select = new simpleSelect($criteria); echo $select->toString(); // выведет "SELECT `field1`, `field2` AS `alias` FROM `table`"
Потому как метод select возвращает ссылку на объект критерии - можно сократить данный код:
$criteria = new criteria('table'); $criteria->select('field1')->select('field2', 'alias'); $select = new simpleSelect($criteria); echo $select->toString(); // выведет "SELECT `field1`, `field2` AS `alias` FROM `table`"
.3 where
Указывает условия выборки
criteria where(string|criterion $field [, mixed $value = null [, int $comparison = criteria::EQUAL]])
$field |
имя поля либо объект criterion (todo ссылка на criterion) |
$value |
сравниваемое значение. В зависимости от условия тип может быть любым |
$comparison |
тип сравнения |
$criteria = new criteria('table'); $criteria->where('field', 'value', criteria::GREATER)->where('field2', 'value2'); $select = new simpleSelect($criteria); echo $select->toString(); // "SELECT * FROM `table` WHERE `table`.`field` > 'value' AND `table`.`field2` = 'value2'"
Доступные для использования типы сравнений:
EQUAL |
= |
NOT_EQUAL |
<> |
GREATER |
> |
LESS |
< |
GREATER_EQUAL |
>= |
LESS_EQUAL |
=< |
IN |
IN |
NOT_IN |
NOT IN |
LIKE |
LIKE |
NOT_LIKE |
NOT LIKE |
BETWEEN |
BETWEEN |
NOT_BETWEEN |
NOT BETWEEN |
FULLTEXT |
MATCH (%s) AGAINST (%s) |
FULLTEXT_BOOLEAN |
MATCH (%s) AGAINST (%s IN BOOLEAN MODE) |
IS_NULL |
IS NULL |
IS_NOT_NULL |
IS NOT NULL |
Для сравнений, в которых операнд "значение" состоит фактически из нескольких значений (Например: IN (1, 2, 3)) необходимо передавать массив этих значений.
Пример работы с такими сравнениями:
$criteria->where('field', array(1, 2, 3), criteria::IN); // `field` IN (1, 2, 3) $criteria->where('field', array(1, 2), criteria::BETWEEN); // `field` BETWEEN 1 AND 2 $criteria->where(array('field1', 'field2'), 'value', criteria::FULLTEXT); // MATCH(`field1`, `field2`) AGAINST ('value') $criteria->where('field', null, criteria::IS_NULL); // `field` IS NULL
.4 orderByAsc, orderByDesc
Добавляет сортировку в запрос
criteria orderByAsc(string $field [, boolean $alias = true]), criteria orderByDesc(string $field [, boolean $alias = true])
$field |
имя поля |
$alias |
алиас |
$criteria = new criteria('table'); $criteria->select('field1'); $criteria->orderByAsc('field1'); $select = new simpleSelect($criteria); $select->toString(); // "SELECT `field1` FROM `table` ORDER BY `table`.`field1` ASC"
Часть 11 Готовые модули
11.1 Страницы
11.2 Меню
11.3 Файловый менеджер
11.4 Комментарии
11.4.1 Обзор
11.4.2 commentsPlugin
Часть 12 Расширение MZZ
12.1 Системные файлы
12.2 Расширение модулей
12.2.1 Mappers
Часто бывает, что необходимо поменять некоторые вещи в коробочных модулях MZZ, например, модифицировать $map (todo: ссылка туда, где знают, что такое MAP объекта) или же просто добавить недостающий метод в маппер. В таком случае подойдет способ, описанный в этой главе, но он не очень удобен по ряду причин.
MZZ предлагает немного иной подход к проблеме переопределения частей модуля в приложениях. Рассмотрим его на примере модуля user.
Мы имеем userMapper.php следующего содержания (тело класса скрыто, весь листинг вы можете посмотреть, открыв файл <MZZ>/system/modules/user/mappers/userMapper.php):
class userMapper extends mapper { /* ... */ } ?>
И мы хотим переопределить этот маппер с целью добавить новый метод searchAllLastLoggedIn — метод, возвращающий нам всех пользователей, которые
приходили на сайт не ранее заданной даты
Для реализации этой идеи достаточно создать файл в каталоге <project>/modules/user/mappers/appUserMapper.php, где <project> это путь до
вашего приложения (опция systemConfig::$pathToApplication), а appUserMapper.php — новое имя маппера, которое
было образовано путем добавления приставки app к базовому имени маппера. Префикс app в данном случае является стандартным и одинаковым для всех переопределяемых мапперов.
appUserMapper.php будет выглядеть примерно так:
fileLoader::load('modules/user/mappers/userMapper'); class appUserMapper extends userMapper { public function searchAllLastLoggedIn($time) { $criteria = new criteria; $criteria->where('last_login', $time, criteria::GREATER); return $this->searchAllByCriteria($criteria); } }
Несложно догадаться, что теперь можно добавлять, переопределять любые методы и свойства базового маппера userMapper
Часть 13 Создание проекта на MZZ
13.1 Введение
MZZ - это инструмент для разработчиков, которые создают веб-приложения на языке PHP. Основная цель MZZ - предоставить разработчикам набор средств для быстрой и гибкой разработки приложений, избавить от необходимости рутинной и монотонной организации окружения и работы веб-приложения: обработки сессий, авторизации и аутентификации пользователей, работы с данными, валидации форм... Вы концентрируетесь лишь на реализации бизнес-логики приложения.
Основные особенности MZZ:
- Код изначально написан под PHP5
- Smarty в качестве шаблонизатора, с рядом реализованных хелперов
- Встроенный ORM
- Генерация кода
- MVC-архитектура
13.2 Архитектура MVC
13.2.1 Model
Компонент Model парадигмы MVC в MZZ представлен собственной реализацией паттерна проектирования The Data Mapper Pattern. Этот паттерн подразумевает два набора классов: мапперы - классы, которые хранят метаописание сущностей, отношения между сущностями, выполняют работу по извлечению из БД и сохранению данных в БД, итд; объекты - контейнеры, которые хранят данные, предоставляют интерфейс для доступа и модификации их.
13.2.2 View
Компонент View определяет отображение данных веб-клиенту. В MZZ для реализации этого компонента используется Smarty 2.6.26. Чаще всего, View хранится в файлах шаблонов, которые содержат HTML и Smarty код.
13.2.3 Controller
Контроллеры являются "прослойкой" между View и Model. Они отвечают на запросы веб-браузера, получают и сохраняют данные, передают модели и другие данные в View.
13.3 Создание блога
13.3.1 Введение
В этом разделе мы опишем процесс создания блог приложения на MZZ: от установки MZZ до состояния готового к использованию приложения. Мы заранее подготовили прототип дизайна блога.

В процессе создания приложения будут созданы две сущности - post, postComment. Из стандартных модулей мы будем использовать только модуль user. Во фреймворке уже имеется готовый модуль комментариев, но, так, как цель данного руководства показать весь процесс написания приложения, мы реализуем комментарии еще раз, с более простой функциональностью.
Наш блог будет обладать следующими действиями: список записей (list), создание записи (create), редактирование записи (edit), удаление записи (delete), создание комментария (comment), список комментариев (comments), удаление комментария (commentDelete).
13.3.2 Установка
Процесс установки самого фреймворка очень прост. Для это необходимость скачать MZZ с Download раздела на официальном сайте и распаковать его в любую директорию.
Код является кроссплатформеным и работает под всеми операционными системами и веб серверами.
13.3.3 Создание Блог приложения
Скачайте dummy (пустой) проект с Download и распакайте его в любую директорию, которая доступна веб-серверу.
Структура типичного веб приложения на MZZ выглядит следующим образом:
| Имя файла | Описание |
|---|---|
| db/ | дампы базы данных приложения |
| files/ | файлы, хранимые модулем fileManager |
| modules/ | модули приложения |
| routes/ | роуты приложения |
| templates/ | шаблоны |
| tmp/ | временные файлы |
| www/ | корневая директория приложения |
| application.php | класс приложения |
| config.php | основная конфигурация приложения |
Корневая директория приложения (www/) - это директория доступная для чтения веб-браузером. В ней хранятся все js/css-файлы, картинки и прочий общедоступный контент. Структура этой директории выглядит так:
| Имя файла | Описание |
|---|---|
| css/ | файлы стилей |
| images/ | файлы изображений |
| js/ | файлы javascript |
| index.php | точка входа в приложение, именно этому файлу передаётся управление |
Мы установили сам фреймворк в директорию /home/MZZ, приложение будет установлено в /home/MZZ/app, веб-сервер настроен на директорию - /home/MZZ/app/www. Адрес нашего проекта будет http://MZZ.blog/.
При необходимости, если фреймворк установлен в отличной от нашего примера директории, в файле config.php можно изменить путь к фреймворку.
define('SYSTEM_PATH', realpath(dirname(__FILE__) . '/../system/'));
Также включим DEBUG-режим (т.к. мы будем писать новый модуль, нам необходимо видеть все ошибки) в том же файле.
define('DEBUG_MODE', true);
tmp директорию.
chown <user>:<group> -R /home/MZZ/app/tmp
chmod 755 -R /home/MZZ/app/tmp
.5 Конфигурация базы данных
Откроем db/mzz_dummy.sql и изменим имя базы данных.
/*!40000 DROP DATABASE IF EXISTS `MZZ_blog`*/; CREATE DATABASE /*!32312 IF NOT EXISTS*/ `MZZ_blog` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `MZZ_blog`;
Импортируем измененный дамп dummy-базы в MySQL:
mysql -u user -ppassword < /home/MZZ/app/db/mzz_dummy.sql
И изменим настройки базы данных все в том же config.php.
systemConfig::$db['default']['dsn'] = 'mysql:host=localhost;dbname=MZZ_blog'; systemConfig::$db['default']['user'] = 'root'; systemConfig::$db['default']['password'] = '';
Если все было сделано правильно и dummy приложение установлено, по адресу http://MZZ.blog/ мы увидим некоторый hello текст.
Hello, world! Привет, мир! Модуль: dummy Экшн: hello
13.3.4 Создание моделей
Теперь нам необходимо создать модуль блог, структуру его директорий и, собственно, сами модели. Для этих целей в MZZ реализован web-интерфейс для управления модулями. Зайти в него можно через административный интерфейс по адресу http://MZZ.blog/admin/ используя стандартый логин и пароль (admin / test). Далее необходимо выбрать пункт меню Администрирование -> Утилиты разработчика. В дальнейшем под словом devToolbar мы будем подразумевать именно этот интерфейс.
Для создания модуля надо нажать на иконку рядом с "Модули и классы". Имя для нового модуля: blog. Каталог генерации: каталог проекта (по умолчанию). Сохраним изменения и, если все успешно, мы увидим текст "Модуль blog успешно создан". Перейдем к моделям.
Модель Post будет содержать 5 свойств: идентификатор (name), заголовок (title), содержимое (content) и дату создания (created_at). Выполним запрос в mysql для создания таблицы:
CREATE TABLE `MZZ_blog`.`blog_post` ( `name` VARCHAR(255) NOT NULL, `title` VARCHAR(255) NOT NULL, `content` TEXT NOT NULL, `created_at` DATETIME NOT NULL, PRIMARY KEY (`name`) ) ENGINE = MyISAM;
В devToolbar, в секции модуля blog, создадим класс post. Имя таблицы blog_post.
В MZZ, действие (action) должно принадлежать какому-либо определенному объекту. Но что делать с действиями list и create? Для этого мы создаем фейковый объект и называем его, как правило, именем модуля или <имяМодуля>Folder. Создавать таблицу в базе данных необходимости нет, поэтому просто создадим класс postFolder для blog и укажем несуществующую таблицу blog_postFolder.
Для комментариев создадим только один объект postComment. Они тоже имеют действия, не связанные с конкретным комментарием, это comments и comment, но в нашем случае для этих целей вполне подойдет объект post, тем более, что комментирование относится к постам в блоге. Начем с создания таблицы в базе данных:
CREATE TABLE `MZZ_blog`.`postComment` ( `id` INT NOT NULL , `post_id` INT NOT NULL , `author_name` VARCHAR( 255 ) NOT NULL , `content` TEXT NOT NULL , `created_at` INT NOT NULL , PRIMARY KEY ( `id` ) , INDEX ( `post_id` ) ) ENGINE = MYISAM ;
Создаем класс postComment для модуля blog в devToolbar указав имя таблицы blog_postComment.
13.3.5 Создание записи в блоге
В MZZ встроен механизм скаффолдинга, который позволяет генерировать готовый к использованию код для стандартных действией над объектом (CRUD - Create, Retrive, Update, Delete). Действия Create и Update чаще всего очень похожи, поэтому мы их объединили в одно Save действие. Здесь надо заметить, что использование CRUD подходит только в довольно общих и простых случаях, и получившийся код в любом случае необходимо дорабатывать под себя.
Первое действие, которое мы создадим, будет создание записи в блоге (create). Как было написано ранее, действие должно принадлежать существующему объекту, поэтому оно будет принадлежать классу postFolder. Создаем это действие в devToolbar указав CRUD: save и CRUD class: post (т.к. на самом деле это действие для создания объекта типа post)
По адресу http://MZZ.blog/blog/create находится автоматически сгенерированная форма для добавления записи. Отредактируем ее чуть-чуть.
Откроем файл шаблона modules/blog/templates/create.tpl и приведем нашу формочку в более приятный вид. В MZZ есть генератор форм, который генерирует HTML-код для различных элементов форм, выставляет дефолтные значения и восстанавливает введеные пользоваталем данные в форме в случае ошибки заполнения формы и отображает ее:
<h3>{if $isEdit}Edit{else}Create{/if} a post</h3> {form action=$form_action method="post"} <table width="100%" border="0" cellpadding="5" cellspacing="0" align="center"> <tr> <td>{form->caption name="post[name]" value="Name"}</td> <td> {form->text name="post[name]" size="30" value=$post->getName()} {if $validator->isFieldError('post[name]')}<div class="error">{$validator->getFieldError('post[name]')}</div>{/if} </td> </tr> <tr> <td>{form->caption name="post[title]" value="Title"}</td> <td> {form->text name="post[title]" size="30" value=$post->getTitle()} {if $validator->isFieldError('post[title]')}<div class="error">{$validator->getFieldError('post[title]')}</div>{/if} </td> </tr> {if !$isEdit} <tr> <td>{form->caption name="post[created_at]" value="Created at"}</td> <td> {form->text name="post[created_at]" size="30" value=$smarty.now|date_format:"%H:%M:%S %d/%m/%Y"} {if $validator->isFieldError('post[created_at]')}<div class="error">{$validator->getFieldError('post[created_at]')}</div>{/if} </td> </tr> {/if} <tr> <td valign="top">{form->caption name="post[content]" value="Content"}</td> <td> {form->textarea name="post[content]" rows="10" cols="45" value=$post->getContent()} {if $validator->isFieldError('post[content]')}<div class="error">{$validator->getFieldError('post[content]')}</div>{/if} </td> </tr> <tr> <td> </td> <td>{form->submit name="submit" value="_ simple/save"}</td> </tr> </table> </form>
Сделаем пару изменений в контроллере modules/blog/controllers/blogCreateController.php. Здесь мы можем изменить валидаторы для нашей формы. Сейчас они выглядит так:
$validator->rule('required', 'post[title]', 'Field title is required'); $validator->rule('length', 'post[title]', 'Field title is out of length', array(0, 255)); $validator->rule('required', 'post[content]', 'Field content is required'); $validator->rule('required', 'post[created_at]', 'Field created_at is required');
Нам они не очень походят, поэтому заменим их на:
$validator->rule('required', 'post[title]', 'Field title is required'); $validator->rule('required', 'post[name]', 'Field name is required'); $validator->rule('required', 'post[content]', 'Field content is required'); $validator->rule('required', 'post[created_at]', 'Field created_at is required'); $validator->rule('regex', 'post[name]', 'Field name has illegal characters', '#^[a-z\d-_]+$#i'); $validator->rule('length', 'post[name]', 'Field name is out of length', array(0, 255)); $validator->rule('length', 'post[title]', 'Field title is out of length', array(0, 255)); $validator->rule('date', 'post[created_at]', 'Invalid creation date', array('regex' => 'time_date')); $validator->rule('callback', 'post[name]', 'Post name is not unique', array(array($this, 'checkUniquePostName'), $post));
Перечисляя по порядку, валидаторы определяют следующее: поля title, name, content и created_at обязательны для заполнения, name может содержать только символы a-z, A-Z, 0-9, - и _ (так как это идентификатор), name и title имеют длину не более чем 255 символов, created_at имеет формат вида 14:43:32 20/12/2009 (вместо значения time_date может быть любое указано регулярное выражение для валидации формата даты, мы будем использовать стандарный вид даты для русской локали). Последний валидатор является callback функцией, для валидации значения будет вызван метод checkUniquePostName у текущего контроллера. Используется он для проверки того, что запись с таким же именем уже не существует. Добавим этот метод в blogCreateController.
public function checkUniquePostName($name, $post) { if ($name == $post->getName()) { return true; } $postMapper = $this->toolkit->getMapper('blog', 'post'); return is_null($postMapper->searchByKey($name)); }
Добавим установку имени для записи:
$post->setName($data['name']); $post->setTitle($data['title']); $post->setContent($data['content']); $post->setCreatedAt($data['created_at']);
Также изменим способ редиректа после сохранения записи.
return jipTools::redirect();
Наше действие не будет выполняться как JIP-действие (об этом мы расскажем позже) поэтому заменим на:
$url = new url('withAnyParam'); $url->add('name', $post->getName()); $url->add('action', 'view'); return $this->redirect($url->get());
Класс url генерирует готовые ссылки используя роутер withAnyParam и переданные в него значения. Метод $this->redirect() выполняет HTTP-редирект пользователя на указанную страницу (просмотр созданной записи).
Чтобы сохранить дату создания в правильном формате, добавим метод в modules/blog/models/post.php
public function setCreatedAt($value) { $datetime = explode(' ', $value); $date = explode('/', $datetime[1]); $datetime = implode('-', array_reverse($date)) . ' ' . $datetime[0]; parent::__call('setCreatedAt', array($datetime)); }

13.3.6 Routers
Маршрутизация (Routing) - это процесс поиска подходящего роута и превращения с его помощью запрошенного пути в обычный массив. Правила маршрутизации хранятся в файле <project_folder>/routers/default.php и могут быть переопределены. Так же у модулей могут быть указаны свои правила маршрутизации, которые могут быть описаны в классе модуля в файле modules/<module>/<module>Module.php.
В modules/blog/blogModule.php мы добавим простой роут обработки путей для конкретной записи в блоге:
public function getRoutes() { return array( array(), array( 'blogPost' => new requestRoute('blog/:name/:action', array( 'module' => 'blog', 'action' => 'view'), array( 'name' => '.*?', 'action' => '(?:create)')))); }
Этот метод возвращает массив из двух элементов: первый содержит роуты, которые будут вызываны после других и второй - перед остальными. Этот простой роут определяет правила для разбора пути вида "blog/first_blog_entry". В MZZ по умолчанию определены некоторые стандартные роуты, в том числе и для обработки путей вида module/name/action (withAnyParam). Смысл создания нашего в том, чтобы роутер знал что "blog/create" это создание новой записи, а не просмотр записи с именем "create".
13.3.7 Просмотр записи в блоге
Если создание записи прошло успешно (для примера, мы создали запись с именем "welcome"), то после добавления мы увидим исключение Unknown action view in module blog. Создадим в devToolbar действие view для класса post выбрав CRUD: view.
По адресу http://MZZ.blog/ru/blog/welcome мы увидим 404 ошибку. Произошло это из-за того, что по умолчанию контроллер blogViewController сгенерировался для работы с числовым идентификатором (id):
$id = $this->request->getInteger('id'); $post = $postMapper->searchByKey($id);
В нашем же случае используется строковое имя для идентификации объекта (name). Исправим это:
$name = $this->request->getString('name'); $post = $postMapper->searchByKey($name);