Szeroki wybór możliwości połączenia PHP i Flexa możemy uważać za coś pozytywnego, ponieważ każdy może sobie odnaleźć swój sposób integracji server-side z client-side. Jednakże na długą metę okazuje się to zmorą, bo człowiek (przynajmniej ja tak mam) zaczyna się zastanawiać, czy to drugie rozwiązanie nie jest lepsze.
Dlatego postanowiłem znaleźć jakiś sposób integracji, który będzie szybki i dobry :). Przeglądając różne stronki o Flexie, odnalazłem prezentację (niestety nie pamiętam adresu), na której ładnie przedstawiony był fakt, że AMF to idealny protokół komunikacji z Flexem. Nic dziwnego, w końcu to twór Adobe.
Ok, protokół mamy. Teraz wypada wybrać implementację. Jako, że jestem użytkownikiem frameworka Symfony, to właśnie z nim chciałbym zintegrować Flexa.
Wiem, że istnieje plugin do symfony sfAmfPlugin (strona głowna). Nie będe się o nim rozpisywał. Jak ktoś jest ciekawy, to niech śmiga na jego stronę główną. Powiem tylko, że jest on oparty na bibliotece SabreAMF, której szczerze mówiąc nie znam. Wiem natomiast, że biblioteka Zend’a jest dobra do wsyztskiego :), dlatego właśnie z niej korzystam.
Poniżej przedstawiam opis jak zrobić szybko HelloWorld we Flexie, z wykorzystaniem Symfony i AMF oraz bibliotek Zenda. We wszystkich HelloWorld’ach w necie brakowało mi tego połączenia.
Zaczynamy
Na początku trzeba postawić zwykły projekt Symfony i dodać jakąś aplikację – ja dodałem aplikacje o nazwie frontend. Zakładam, że ludzie czytający wpis o symfony wiedzą jak to zrobić :)
Następnie należy zintegrować odpowiednie biblioteki Zenda. Integracja została opisana we wpisie Integracja Symfony + Zend.
Oto lista plików jakie należy umieścić w katalogu /lib/vendor/Zend:
- Amf/
- Loader/
- Server/
- Auth.php
- Loader.php
- Exception.php
Następnie tworzymy moduł amf, w którym będzie nasz Zend_Amf_Endpoint, czyli miejsce do którego będziemy się odwoływać z Flexa.
Ja umieściłem swój endpoint w akcji server (tak dla niepoznaki ;)). Oto kod akcji:
public function executeServer(sfWebRequest $request) {
$server = new Zend_Amf_Server();
$server->setClass('helloService');
$this->renderText($server->handle());
return sfView::NONE;
}
Nic innego jak stworzenie instancji serwera, przypisanie mu klas, które są service’ami oraz wyświetlenie wyniku metody handle(). Istotne jest też wyłączenie renderowania template’u.
Services
Jak pisałem w opisie akcji, serwer Zend_Amf_Server składa się z klas (lub funkcji). Najwygodniejszym sposobem jest stworzenie oddzielnych klas, które będziemy traktować jako przestrzenie nazw. Poniżej nasza klasa dla helloWorld.
class helloService {
/**
* Method returns hello mesage
*
* @return string
*/
public function helloWorld() {
return 'hello world';
}
}
Jak widać service hello posiada tylko jedną metodę bezparametrową helloWorld(), która zwraca string. AMF to potęga bo mapuje typy :), więc we Flexie nie trzeba będzie parsować odpowiedzi, jak by to miało miejsce w przypadku np. XML czy JSON. Należy jednak określic w PHPDoc (nad metodą) wszystkie parametry oraz zwracany typ. Dobrą praktyką jest też krótkie opisanie działania metody.
Flex
Przed rozpoczęciem implementowania trzeba dodać do projektu (do folderu src) plik services-config.xml, który jest konfiguracją połączenie Flexa z endpointem. Założmy, że nasz serwer amf znajduje się pod adresem:
Wtedy przykładowy plik services-config.xml wygląda następująco
<?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/helloServer/amf/server" class="flex.messaging.endpoints.AMFEndpoint"/> </channel-definition> </channels> </services-config>
Nastepnie należy wejść do ustawień projektu. Przejść do “Flex compiler” i w polu “Additonal compiler arguments” dopisać:
Komunikacja
Z poziomu flexa do komunikacji będziemy wykorzystywać klasę RemoteObject. Poniżej przedstawiam przykład klasy fasady do komunikacji z serwerem PHP oraz jej wykorzystanie w przykładowej aplikacji. Fasada posiada obiekt RemoteObject, do którego mamy dostęp zabroniony. Wszelkie zapytania do serwera AMF będą przechodziły przez nasza fasadę.
public class ServerFacade {
private var helloService:RemoteObject = null;
public function ServerFacade() {
//połączenie z endpointem oznaczonym w pliku services-config.xml
helloService = new RemoteObject("amfphp");
helloService.showBusyCursor = true;
//przypisanie nazwy klasy do źródła
helloService.source = "helloService";
//określenie co ma sie dziać jak wystąpi błąd
helloService.addEventListener(FaultEvent.FAULT, globalFault);
//przypisanie Listenerów, które są wywoływaja jak zostanie zwrócona wartość z serwera
addApiListeners();
}
//METHODS
public function hello_helloWorld():void {
helloService.helloWorld();
}
//HELLO_WORLD
public var hello_helloWorld_resultHandler:Function = null;
private function hello_helloWorld_internalResultHandler(event:ResultEvent):void {
if (hello_helloWorld_resultHandler != null) {
hello_helloWorld_resultHandler(event);
}
}
//PRIVATE
private function addApiListeners():void {
helloService.helloWorld.addEventListener(ResultEvent.RESULT, hello_helloWorld_internalResultHandler);
}
private function globalFault(event:FaultEvent):void {
Alert.show(event.fault.message, "Fault: " + event.type);
}
}
Jak widać funkcja helloWorld została połączona z dwoma resultHandlerami. Jest to mój sposób implementacji asynchronicznych zapytań do serwera. Oczywiście asynchroniczność powoduje, że nie otrzymujemu wyniku od razu, tylko musimy dodać Listener’a, który obsłuży zwracany wynik (w bliżej nieokreślonym czasie).
Wewnętrzny handler (internalHandler) nasłuchuje na zdarzenie. Gdy je przechwyci:
- Wykonuje wewnętrzne akcję (przykład podam poniżej)
- Wywołuje funkcję zewnętrzną ‘hello_helloWorld_resultHandler‘, jeśli została zdefiniowana przez użytkownika.
Przykład zastosowania fasady jest następujący:
var sF:ServerFacade = new ServerFacade();
sF.hello_helloWorld_resultHandler = function(event:ResultEvent) {
Alert.show(event.result);
}
sF.hello_helloWorld();
Przed wywołaniem funkcji określamy, co ma zostać wykonane z jej wynikiem – wyświetlenie w boxie Alert. Następnie wywołujemy funkcję.
Przykładem wykorzystania wewnętrznych handlerów jest zachowanie stanu sesji uzytkownika. Na przykład w wewnętrznym handlerze akcji logowania można zapisać rolę użytkownika i wynik innych metod odpowiednio modyfikować w zależności od roli zalogowanego użytkownika.
Niestety nie mam tego przykładu jeszcze popartego kodem, ale jak tylko będę mógł pokazać kod, który wykorzystuje taki mechanizm, to umieszczę go na blogu.