Главная > Flex, Frameworks > Строим приложение на Mate framework + Zend_AMF

Строим приложение на Mate framework + Zend_AMF

В своем первом посте я бы хотел познакомить читателей с относительно молодым, легким и мощным Flex-фреймвёрком — Mate.

Сегодня мы создадим простое RIA приложение на основе Flex и Mate framework на стороне клиента, и PHP и Zend framework (в частности компонент Zend_AMF) на стороне сервера. Назовем его MateTrial, пусть это будет менеджер контактов.

Введение

В свое время я успел поработать с неповоротливыми Cairngorn и PureMVC, среди преимуществ которых можно выделить лишь «строгость» к архитектуре приложения. Это особенно актуально для долгих проектов, крупных компаний с большой текучкой кадров — новый сотрудник быстро разберется в дебрях приложения, всегда ясно где что лежит и что чем управляет. На этом плюсы заканчиваются. С добавлением новой функциональной единицы в приложение код вырастает в шестикратном размере. Нужно ли вам это?

Немного забегу вперед показав внешний вид будущего приложения. Вот, что должно получиться:

Flex mate framework and zend amf

Flex mate framework and zend amf

Шаг 1. Структура Mate приложения

Итак, начнём со структуры проекта. Разработчики Mate рекомендуют немного иной подход, но я изображу более удобный для меня.

Mate project structure

Сердце приложения расположено в 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.
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.

Flex, Frameworks ,

  1. FG
    20 Июль 2009 из 19:45 | #1

    Спасибо, отличная статья. Все никак не решался подойти к Mate. А после прочтения вашей статьи, руки зачесались )

  2. 20 Июль 2009 из 22:16 | #2

    Спасибо за статью очень понравилось.

    Но вот про Mate мало чего понятного.

    Зато понравился и удевил Zend_AMF в действии.

    Интересно, в AMF_PHP тоже можно создавать ClassMap?

  3. 21 Июль 2009 из 11:23 | #3

    Спасибо, на самом деле Mate очень мощный инструмент. Алексей, а что именно не ясно относительно Mate? Задавайте вопросы, я постараюсь ответить.

  4. Сергей
    11 Авг 2009 из 0:19 | #4

    Спасибо за статью, в деме не работает русский язык.

  5. Сергей
    16 Авг 2009 из 22:27 | #5

    Сергей :

    Спасибо за статью, в деме не работает русский язык.

    Всё работает , это я просто ещё не знал что под убунтой есть такой глюк плеера... сорри если кого смутил

  6. 22 Ноя 2009 из 21:34 | #6

    Евгений, а есть еще примеры по Mate? Для начала что нить простое... И еще вопрос по ZendAMF — у него есть встроенный браузер объектов по аналогии с AMFPHP? Я с Zend никогда не работал.

  1. Трекбеков пока нет.