User's collector

Внимание!   Данная опция будет доступна только после того, как вы авторизуетесь.
   запомнить меня 
13 марта 2007

Flex RemoteObject и AMFPHP 1.9

Многие люди, работающие в сфере RIA, знают, что Patrick Mineault недавно выпустил новую версию amfphp, поддерживающую Flex 2. В этой статье мы узнаем, как установить новую версию amfphp и как работать во Flex 2 с RemoteObject в виде mxml-тэга, используя amfphp. И так, приступим.

Оригинал статьи: http://www.sephiroth.it/tutorials/flashPHP/flex_remoteobject/index.php.
Автор: Alessandro Crugnola
Перевод: Алексей «Vooparker» Аникутин.
Редактор: Юрий Яровой.

1. Загрузка и установка amfphp 1.9

Сначала скачайте архив с amfphp 1.9 (на момент перевода статьи вышла версия amfphp 1.9 beta 2 — прим. перевод.) и распакуйте его в корневую директорию на вашем хосте (например, http://localhost/amfphp2).

2. Создание первого сервиса

В директории amfphp2/services создайте новую папку tutorials (в ней мы будем размещать наши php-классы). Теперь, в этой папке создайте файл HelloWorld.php:

PHP:
  1. <?php
  2.  
  3. /**
  4. * Простой учебный класс.
  5. */
  6. class HelloWorld {
  7.  
  8.     /**
  9.      * Простой учебный метод.
  10.      * @returns Строка 'Hello World!'
  11.      */
  12.     function sayHello()
  13.     {
  14.         return "Hello World!";
  15.     }
  16. }
  17. ?>

Если вам уже приходилось работать с предыдущими версиями amfphp, то вы, наверное, сразу сразу заметили отсутствие $this->methodTable в конструкторе класса. Method table (список с доступными методами класса и их описанием) был упразднен в версии amfphp 1.9 и теперь попросту игнорируется.

В версии amfphp 1.9 предполагается, что все методы класса могут быть вызванными удаленно до тех пор, если вы не добавляете в начало названия метода символ подчеркивания "_", или идентификатор доступа private (только для php5).

Комментарии в стиле javadoc, использованные в классе как в его описании так и в описании его методов, так же будут отображены в amfphp браузере (что очень удобно, особенно, когда за разработку серверной и клиентской части отвечают разные разработчики — прим. перевод.). Тех кто работал с предыдущими версиями amfphp ожидает сюрприз — новый браузер теперь сделан на Flex 2, с его помощью вы можете исследовать доступные сервисы. Сам браузер находится в директории amfphp2/browser (например, http://localhost/amfphp2/browser).

Браузер сервисов amfphp версии 1.9 beta2 с выбранным сервисом Helloworld.php:
Браузер сервисов AMFPHP 1.9 beta 2
Как вы видите в браузере отображены javadoc-комментарии, описывающие как класс в целом, так и его метод. Как и в предыдущих версиях браузера вы можете протестировать методы сервиса, просто нажав кнопку Call. Если метод требует аргументов, то рядом с кнопкой Call появятся поля ввода для каждого аргумента.

3. Создание Flex 2 проекта.

Создайте новый проект в вашей Flex 2 IDE. Откройте его свойства (правый клик на названии проекта и выберите "Properties"). В окне диалога свойств проекта выберите раздел меню "Flex Build path", затем переключитесь на вкладку "Library Path". Убедитесь, что среди библиотек проекта находится файл "rpc.swc", как показано на рисунке:
Project Library
Теперь нам необходимо сконфигурировать удаленные сервисы для того, чтобы получить возможность вызова их методов. Поэтому создайте в корне нашего flex-проекта файл services-config.xml со следующим содержанием:

<?xml version="1.0" encoding="UTF-8"?>
<services-config>
	<services>
		<service id="amfphp-flashremoting-service"
                               class="flex.messaging.services.RemotingService"
                               messageTypes="flex.messaging.messages.RemotingMessage">
			<destination id="amfphp">
				<channels>

					<channel ref="my-amfphp"/>
				</channels>
				<properties>
					<source>*</source>
				</properties>

			</destination>
		</service>
	</services>
	<channels>
		<channel-definition id="my-amfphp" class="mx.messaging.channels.AMFChannel">
			<endpoint uri="http://localhost/amfphp2/gateway.php"
                             class="flex.messaging.endpoints.AMFEndpoint"/>

		</channel-definition>
	</channels>
</services-config>

Теперь снова открываем свойства проекта и в разделе "Flex Compiler" добавляем следующую строку -services "services-config.xml" (см. картинку):
Compiler settings
И так теперь мы готовы использовать RemoteObjects.

Если вы хотите узнать больше о конфигурационном файле сервисов, тогда вам сюда

3.1 mxml файл

Создайте в проекте новый файл main.mxml, а в нем — очень простой лэйаут (расположение и композиция элементов):

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
backgroundColor="#FFFFFF" viewSourceURL="srcview/index.html">
    <mx:Button x="170" y="157" label="sayHello" width="79"/>
    <mx:Button x="170" y="187" label="test fault"/>
    <mx:TextArea x="10" y="36" width="239" height="113" id="result_text"/>
    <mx:Label x="10" y="10" text="Result:"/>
</mx:Application>

Как вы видите, пока в нашем файле не описано никаких действий, это просто лэйаут для первого теста. У нас есть две кнопки: первая будет вызывать удаленный метод sayHello, вторая — метод, которого нет в вызываемом сервисе, для того, чтобы посмотреть, как осуществляется обработка ошибок. Как в случае успешного вызова, так и в случае неудачи, результат будет отображен в текстовом поле result_text.

Теперь давайте дополним наш mxml-файл тегом RemoteObject, где опишем, какие методы мы можем вызывать из amfphp. После чего наш файл должен выглядеть так:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
backgroundColor="#FFFFFF" viewSourceURL="srcview/index.html">
    <mx:RemoteObject id="myservice" fault="faultHandler(event)"
              showBusyCursor="true" source="tutorials.HelloWorld" destination="amfphp">
        <mx:method name="sayHello" result="resultHandler(event)" />
    </mx:RemoteObject>

    <mx:Script>
        <![CDATA[
            import mx.managers.CursorManager;
            import mx.rpc.events.ResultEvent;
            import mx.rpc.events.FaultEvent;
            private function faultHandler(fault:FaultEvent):void
            {
                CursorManager.removeBusyCursor();
                result_text.text = "code:n" + fault.fault.faultCode + "nnMessage:n" + fault.fault.faultString + "nnDetail:n" + fault.fault.faultDetail;
            }

            private function resultHandler(evt:ResultEvent):void
            {
                result_text.text = evt.message.body.toString(); // same as: evt.result.toString();
            }
        ]]>
    </mx:Script>

    <mx:Button x="250" y="157" label="sayHello" width="79" click="myservice.getOperation('sayHello').send();"/>
    <mx:Button x="250" y="187" label="test fault" click="myservice.getOperation('foo').send(); "/>
    <mx:TextArea x="10" y="36" width="319" height="113" id="result_text"/>
    <mx:Label x="10" y="10" text="Result:"/>
</mx:Application>

Два отступления перед тем как продолжить. Во первых, как вы видите я использовал код myservice.getOperation('sayHello').send();, чтобы вызвать метод sayHello удаленного сервиса. Вы можете сделать тоже самое, используя следующий код myservice.sayHello.send();. Во-вторых, в обработчике успешного вызова я использовал код evt.message.body, чтобы получить доступ к телу пришедшего сообщения. И в этом случае вы можете использовать альтернативный код evt.result для получения точно такого же результата.

Теперь покликайте по кнопкам call и test fault и посмотрите что произойдет. Сверх того, вы можете увидеть, что происходит «за кулисами» нашего приложения, когда вы вызываете удаленные методы, в этом вам поможет Service Capture.

Давайте посмотрим что изменилось. Мы добавили тэг mx:RemoteObject в начале mxml-файла и сделали это следующим образом:

  • id="myservice" — это позволит в коде обращаться к объекту по его id
  • fault="faultHandler(event)" — назначаем метод, который по умолчанию будет обрабатывать ошибки
  • showBusyCursor="true" — этим мы сообщаем нашему приложению, что вовремя вызова удаленного метода, необходимо показывать busy сursor до тех пор, пока не будет получен результат.
  • source="tutorials.HelloWorld" — это «пакетоподобный» путь к нашему php-классу, который, как вы помните, находится здесь /services/tutorials/HelloWorld.php.
  • destination="amfphp" — это ссылка на id тэга <destination> в файле services-config.xml

Теперь, когда мы описали наш remoteObject, добавляем удаленные методы, которые мы хотим вызывать. Для этого добавим тэг mx:method внутрь тэга mx:RemoteObject:
<mx:method name="sayHello" result="resultHandler(event)" />

В этом случае, когда вернется результат вызова метода sayHello, он будет обработан методом приложения resultHandler:

private function resultHandler(evt:ResultEvent):void {
    result_text.text = evt.message.body.toString();
}

Нам придется привести свойство body к типу String, потому как изначально оно определено как Object в интерфейсе IMessage.

Вы так же можете использовать evt.result вместо evt.message.body

3.2 Более сложный пример (RemoteAlias).

Теперь давайте рассмотрим более сложный пример с возвращаемым результатом типа ArrayCollection и использованием метатэга [RemoteClass] во Flex.

В этом примере удаленный метод будет возвращать массив экземпляров класса Person, стуктура которго заранее определена (mapped classes). Это означает, что тип каждого элемента возвращаемого массива соответствует классу с идентичной структурой в actionscript.

Сначала создадим новый php-класс "Person.php" в папке "tutorials":

<?php
class Person {
    var $firstName;
    var $lastName;
    var $phone;
    var $email;
    // explicit actionscript package
    var $_explicitType = "tutorials.Person";
}
?>

Свойство $_explicitType скажет amfphp, что этот класс имеет свой эквивалент "tutorials.Person" во Flex. А вот и сам аналог предыдущего класса в ActionScript (мы должны сохранять структуру пакетов, поэтому класс "Person.as" располагается в директории "tutorials" нашего Flex проекта):

package tutorials
{
    [RemoteClass(alias="tutorials.Person")]
    [Bindable]
    public class Person
    {
        public var firstName:String;
        public var lastName:String;
        public var phone:String;
        public var email:String;
    }
}

Метатэг RemoteClass укажет Flex'у, какой удаленный объект ассоциирован с этим классом (важно помнить, что оба класса должны иметь одинаковую структуру, иначе Flex не сможет правильно привести к нужному типу удаленный объект).

Теперь создадим php-класс, который будет являться нашим основным сервисом - "PersonService.php". В нем будет всего лишь один открытый метод "getList", который возвращает массив состоящий из экземпляров класса Person:

<?php

require_once "./Person.php";

class PersonService
{
    /**
     * Get a list of people
     * @returns An Array of Person
     */
    function getList()
    {
        $people = array(
            array("Alessandro", "Crugnola", "+390332730999", "alessandro@sephiroth.it"),
            array("Patrick", "Mineault", "+1234567890", "patrick@5etdemi.com"),
        );

        $p = array();

        for($a = 0; $a < count($people); $a++){
            $person = new Person();
            $person->firstName = $people[$a][0];
            $person->lastName = $people[$a][1];
            $person->phone = $people[$a][2];
            $person->email = $people[$a][3];
            $p[] = $person;
        }

        return $p;
    }
}
?>

Теперь перейдем к исходному коду mxml-файла.

mxml-файл

Это код main.mxml нашего нового примера:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
backgroundColor="#FFFFFF">
    <mx:RemoteObject id="myservice" source="tutorials.PersonService" destination="amfphp"
fault="faultHandler(event)" showBusyCursor="true">
        <mx:method name="getList" result="getListHandler(event)" fault="faultHandler(event)" />
    </mx:RemoteObject>
    <mx:DataGrid x="10" y="10" width="345" id="people_list" dataProvider="{dp}"
change="changeHandler(event)">
        <mx:columns>
            <mx:DataGridColumn headerText="Last name" dataField="lastName"/>
            <mx:DataGridColumn headerText="First name" dataField="firstName"/>
            <mx:DataGridColumn headerText="Telephone" dataField="phone"/>
            <mx:DataGridColumn headerText="Email" dataField="email"/>
        </mx:columns>
    </mx:DataGrid>

    <mx:Script>
        <![CDATA[
            import mx.utils.ArrayUtil;
            import tutorials.Person;
            import mx.collections.ArrayCollection;
            import mx.rpc.events.ResultEvent;
            import mx.controls.Alert;
            import mx.rpc.events.FaultEvent;

            [Bindable]
            private var dp:ArrayCollection;

            [Bindable]
            private var selectedPerson:Person;

            private function faultHandler(fault:FaultEvent):void
            {
                Alert.show(fault.fault.faultString, fault.fault.faultCode.toString());
            }

            private function getListHandler(evt:ResultEvent):void
            {
                dp = new ArrayCollection( ArrayUtil.toArray(evt.result) );
            }

            private function changeHandler(event:Event):void
            {
                selectedPerson = Person(DataGrid(event.target).selectedItem);
            }
        ]]>
    </mx:Script>
    <mx:Button x="290" y="357" label="get list" click="myservice.getOperation('getList').send();"/>
    <mx:Form x="10" y="174" width="345" height="175">
        <mx:FormHeading label="Selected Person" />
        <mx:FormItem label="First Name">
            <mx:TextInput id="person_first_name" text="{selectedPerson.firstName}" />
        </mx:FormItem>
        <mx:FormItem label="Last Name">
            <mx:TextInput id="person_last_name" text="{selectedPerson.lastName}" />
        </mx:FormItem>
        <mx:FormItem label="Telephone">
            <mx:TextInput id="person_phone" text="{selectedPerson.phone}" />
        </mx:FormItem>
        <mx:FormItem label="Email">
            <mx:TextInput id="person_email" text="{selectedPerson.email}" />
        </mx:FormItem>
    </mx:Form>
</mx:Application>

В тэге RemoteObject, мы указываем в качестве сервиса используется класс tutorials/PersonService.php, внутри тэга описываем один метод getList, который вызывает метод-тёску класса PersonService.php.

Обработчик удачного вызова преобразует результат, полученный от php, в ArrayCollection и записывает его в переменную "dp".

Источник данных компонента DataGrid «связан» с приватной переменной "dp" (ArrayCollection) (перед объявлением "dp" стоит метатэг [Bindable], это значит, что данные в DataGrid изменяться как только они будет получены от php).

Другая переменная "selectedPerson", которая также обозначена как [Bindable], меняется как только будет выбран новый элемент в DataGrid (см. метод "changeHandler"). Свойства "selectedPerson" (firtsname, lastname и др.) отображаются в элементах формы.

И наконец, кнопка, которая вызывает удаленный метод getList, используя для этого следующий код:

click="myservice.getOperation('getList').send();"

AMFPHP 1.9 содержит много изменений, что позволяет корректно работать с flex2/Data services. Вы можете узнать больше о новых релизах и обо всех новых возможностях и изменениях здесь.
Помните, что в статье рассмотрена только бета-версия amfphp2 и многие вещи могут измениться в будущем (надеемся, что будет реализована полная поддержка всех возможностей Flex Data Services 2).

От переводчика: Спасибо автору статьи Alessandro Crugnola за разрешение на перевод и хозяину этого блога Юрию Яровому за публикацию перевода.

7 комментариев