Строим приложение на Mate framework + Zend_AMF
В своем первом посте я бы хотел познакомить читателей с относительно молодым, легким и мощным Flex-фреймвёрком — Mate.
Сегодня мы создадим простое RIA приложение на основе Flex и Mate framework на стороне клиента, и PHP и Zend framework (в частности компонент Zend_AMF) на стороне сервера. Назовем его MateTrial, пусть это будет менеджер контактов.
Введение
В свое время я успел поработать с неповоротливыми Cairngorn и PureMVC, среди преимуществ которых можно выделить лишь «строгость» к архитектуре приложения. Это особенно актуально для долгих проектов, крупных компаний с большой текучкой кадров — новый сотрудник быстро разберется в дебрях приложения, всегда ясно где что лежит и что чем управляет. На этом плюсы заканчиваются. С добавлением новой функциональной единицы в приложение код вырастает в шестикратном размере. Нужно ли вам это?
Немного забегу вперед показав внешний вид будущего приложения. Вот, что должно получиться:
Шаг 1. Структура Mate приложения
Итак, начнём со структуры проекта. Разработчики Mate рекомендуют немного иной подход, но я изображу более удобный для меня.
Сердце приложения расположено в package ru.flexdev в директориях business, events, maps, views. Поясню, что сие значит. Flex Mate — это также MVC фреймвёрк, в котором могут присутствовать model, view и controller. Так вот сама бизнес модель должна располагаться в директории business. Туда помещаем классы, которые отвечают за бизнес логику приложения, которые, например, будут обрабатывать данные, пришедшие от сервиса, а также виртуальные объекты, которые мы поместим в ru.flexdev.business.vo. В директории events расположены наши события. Mate — это event-driven фреймвёрк, он упращает работу с такими возможностями флекса, как генерация событий и биндинг данных. Наши события — это классы, которые расширяют функционал flash.events.Event, таким образом мы можем генерировать события нужного нам типа, которые будут иметь определённые свойства. С views думаю всё понятно, там расположены файлы отвечающие за представление — GUI. Самое интересное находится в папке maps — MainEventMap.mxml — это карта событий, в которой мы указываем обработчики тех или иных событий, связываем между собой Bindable переменные разных классов, работаем с сервисами и многое другое.
В папке assets сложим всю графику, стили, конфиги и прочее. Также вам потребуется скачать билд Mate Framework, последняя версия которого доступна по адресу http://mate.asfusion.com/page/downloads, его следует поместить в директорию libs, и включить в build path, если вы не используете FlexBuilder.
Шаг 2. AMF Server. Создание модели данных
Наше приложение MateTrial — будет трёхзвенным: клиент — сервер — база данных. В качестве хранилища данных я выбрал SQLite, т.е. требования к БД у нас минимальны, нужно просто быстро работать с небольшими объёмами информации, выполняя типичные операции CRUD-приложения.
Создадим базу данных contacts.sqlite в которой будет всего одна таблица:
CREATE TABLE "contacts" ( "id" INTEGER PRIMARY KEY NOT NULL , "firstname" varchar(255) NOT NULL , "lastname" varchar(255) NOT NULL , "birthdate" date NOT NULL , "phone" varchar(255) NOT NULL , "group" varchar(255) NOT NULL);
Для работы с SQLite можете установить плагин SQLite Manager для Firefox. Или вы можете сразу скачать файл базы данных из моих исходников.
Наше PHP-приложение будет находиться в папке amf-server.

Я не буду заострять внимание на создании ZF-приложения, рассмотрим самое интересное — object mapping. В главном (bootstrap) файле приложения мы создаём новый AMF-server, указываем с каким классом сервисов будет работать сервер, устанавливаем ClassMap, связывая тип объекта в PHP и Flex, и запускаем сервер.
// index.php $server = new Zend_Amf_Server(); $server->setClass('ContactManager'); $server->setClassMap('ru.flexdev.business.vo.ContactVO', 'Contact'); $response = $server->handle(); echo $response;
Стоит обратить внимание на $server->setClassMap — маппинг объектов между клиентом и сервером. Посмотрим как это работает. В директории core создадим Contact.php:
class Contact { public $contactId; public $firstName; public $lastName; public $birthDate; public $phone; public $group; }
Создадим соответствующий ему AS-класс ContactVO.as:
package ru.flexdev.business.vo { [Bindable] [RemoteClass(alias="ru.flexdev.business.vo.ContactVO")] public class ContactVO { public var contactId:int; public var firstName:String; public var lastName:String; public var birthDate:String; public var phone:String; public var group:String; } }
Таким образом ZF и флекс позволяют обмениваться объектами типа Contact с автоматическим type-cast между клиентом и сервером. Можно заметить насколько это удобно, заглянув в код класса ContactManager.php, методы которого вызываются из Flex и напрямую передаётся объект Contact:
class ContactManager { private $_model; public function __construct() { $this->_model = new ContactsModel(); } public function loadAllContacts() { $data = $this->_model->fetchAll(); return $this->_createDataProvider($data); } public function createContact(Contact $contact) { $data = $this->_createData($contact); $this->_model->insert($data); return $this->loadAllContacts(); } public function updateContact(Contact $contact) { $data = $this->_createData($contact); $where = $this->_model->getAdapter()->quoteInto('id = ?', $contact->contactId); $this->_model->update($data, $where); return $this->loadAllContacts(); } public function removeContact(Contact $contact) { $where = $this->_model->getAdapter()->quoteInto('id = ?', $contact->contactId); $this->_model->delete($where); return $this->loadAllContacts(); } public function filterContacts($dateFrom, $dateTo, $group) { $data = $this->_model->filterContacts($dateFrom, $dateTo, $group); return $this->_createDataProvider($data); } private function _createData(Contact $contact) { $data = array(); $data['firstname'] = $contact->firstName; $data['lastname'] = $contact->lastName; $data['birthdate'] = $contact->birthDate; $data['phone'] = $contact->phone; $data['group'] = $contact->group; return $data; } private function _createDataProvider($data) { $return = array(); foreach($data as $item) { $contact = new Contact(); $contact->contactId = $item['id']; $contact->firstName = $item['firstname']; $contact->lastName = $item['lastname']; $contact->birthDate = $item['birthdate']; $contact->phone = $item['phone']; $contact->group = $item['group']; $return[] = $contact; } return $return; } }
Шаг 3. EventMap — сердце Mate приложения
Сердцем Mate приложения являются так называемые карты событий. Это MXML файлы, описывающие биндинг переменных, обработку событий, дебаг и т.п., попросту говоря — в картах событий содержится информация «что нужно делать приложению, когда диспатчится определённое событие» или «связь (биндинг) между переменными модели и вью». Рассмотрим пример:
<EventHandlers type="{ContactEvent.CREATE_CONTACT}"> <RemoteObjectInvoker instance="{remoteService}" method="createContact" arguments="{event.contactVO}"> <resultHandlers> <EventAnnouncer type="{FilterEvent.RELOAD}" generator="{FilterEvent}" /> </resultHandlers> </RemoteObjectInvoker> </EventHandlers>
Это обработчик события типа «createContact», при диспатче которого вызывается метод экземпляра mx:RemoteObject, в качестве аргументов мы передаем свойство contactVO, принадлежащее классу ContactEvent.
Внутри тегов resultHandlers мы помещаем код, отвечающий за обработку результатов — в данном случае это диспатч нового события.
Если вам необходимо «слушать» событие и обрабатывать его во view, вы можете добавить листенер в ваш MXML файл:
<mate:Listener type="{FilterEvent.RELOAD}" receive="{filterContacts(event)}" />Также карты событий предоставляют еще одну удобную функцию — Injection. Это биндинг переменных между экземплярами класса модели и представления. Создав Bindable переменные в модели и вью, мы обеспечиваем их биндинг с помощью карты событий — как только переменная изменяется в модели, эти изменения отражаются в представлении.
<Injectors target="{MainGUI}"> <PropertyInjector targetKey="contactList" source="{ContactManager}" sourceKey="contactList" /> </Injectors>
Итак, в преведенном коде мы связали переменные contactList модели и представления. Это очень удобно, Mate использует нативные инструменты Flex, и избавляет разработчиков от диспатчинга лишних событий после изменения преременных, он также сам создает единственный экземпляр модели (что-то вроде синглтона).
Последнее, что хотелось бы добавить относительно Mate framework, это работа с веб-сервисами, http-сервисами и RPC. Для этого существует ряд удобных утилит, простые врапперы, которые используются внутри карт событий:
Основная прелесть состоит в том, что программирование сервисов, обработка результатов и ошибок осуществляется в родном MXML. Ведь Mate — это tag-based фреймвёрк
Послесловие
Я постарался максимально сжато изложить концепт проекта, построенного на Mate и Zend framework. Естественно, данный проект создан лишь как пример, для знакомства с фреймверком, он не соответствует паттерну MVC на 100%, я допустил много упрощений. В идеале всё «общение» компонентов приложения должно происходить только через события и биндинг. В больших приложениях EventMap может разрастаться до значительных размеров, поэтому имеет смысл разделить его на несколько файлов согласно бизнес-логике, также в отдельный файл вынести Injections.
Если вы не используете faultHandlers внутри EventHandlers, вы можете добавить общий обработчик ошибок для всего приложения. В карту событий добавьте обработчик:
<!-- Application-wide fault handler --> <EventHandlers type="{UnhandledFaultEvent.FAULT}"> <MethodInvoker generator="{ApplicationManager}" method="handleFault" arguments="{event.fault}" /> </EventHandlers>
Теперь при попадании FaultEvent в поток событий Flex будет вызываться метод handleFault () класса ApplicationManager, с параметром event.fault.
Готовый проект в действии можно посмотреть по этой ссылке.
Исходный код проекта, ключая php, flex и sqlite исходники можно скачать тут.
Живая версия amf-сервера, выложенная на моём сайте отличается от той, что лежит в архиве, введены некоторые ограничения в целях безопасности. Также вы не можете создать более 20 контактов. Остальные действия доступны.
PS: С удовольствием отвечу на ваши вопросы касательно этой статьи и использования Mate framework.


Спасибо, отличная статья. Все никак не решался подойти к Mate. А после прочтения вашей статьи, руки зачесались )
Спасибо за статью очень понравилось.
Но вот про Mate мало чего понятного.
Зато понравился и удевил Zend_AMF в действии.
Интересно, в AMF_PHP тоже можно создавать ClassMap?
Спасибо, на самом деле Mate очень мощный инструмент. Алексей, а что именно не ясно относительно Mate? Задавайте вопросы, я постараюсь ответить.
Спасибо за статью, в деме не работает русский язык.
Всё работает , это я просто ещё не знал что под убунтой есть такой глюк плеера... сорри если кого смутил
Евгений, а есть еще примеры по Mate? Для начала что нить простое... И еще вопрос по ZendAMF — у него есть встроенный браузер объектов по аналогии с AMFPHP? Я с Zend никогда не работал.