...
...

Дорога из желтого кирпича: Flash 8 & SWX & PHP & MYSQL

Я продолжаю знакомить вас с различными технологиями в мире flash, позволяющими быстрее и проще (без глупых ошибок) создавать сложные приложения. Тема сегодняшнего материала — организация взаимодействия между flash и серверными скриптами, написанными на php.

Самое сложное в написании статьи — доказать читателю, зачем я выношу на его суд ту или иную технологию. Почему я решил рассказать о библиотеке X, а не Y? Большей частью этот выбор субъективен (объективно плохие продукты живут на моем компьютере очень недолго). Библиотек для организации удаленных вызовов из flash логики, реализованной на серверных языках (php, java, .net) не так уж и много (хотя десяток можно легко насчитать). Некоторые из них делают серьезные коммерческие компании, некоторые ваяют (как говорится, на коленке) компьютерные энтузиасты. Используются различные форматы передачи данных, стандартизированные (читай: рекомендуемые) и не очень. Но в одном они сходятся. С тем, как реализован механизм вызова серверной логики и передача сложных структур данных из flash на сервер и обратно, мириться просто нельзя. Если у вас flash8 в стандартной поставке, то вам придется обойтись только XML, LoadVars. И если передать данные на сервер еще кое-как можно, то принять в ответ сложную структуру данных достаточно "неудобно". "Неудобство" проявляется в повышенном числе потенциальных ошибок (выявлению которых не очень способствует среда разработки flash), возникающих в ходе операций "принять, разобрать и переместить" информацию из некоторого входного формата в ваши переменные или визуальные элементы управления. Например, что вы будете делать, если php-скрипт должен вернуть массив объектов Department (отдел), в каждом из отделов есть множество объектов Human (сотрудник), а у каждого из сотрудников, в свою очередь, есть массив из объектов Salary(Зарплата)? Вообще-то такие сложные структуры данных принято кодировать в виде xml-документов. Но с другой стороны в actionscript2 нет встроенной поддержки xpath, а выполнять разбор данных с помощью бесконечно вложенных firstChild, childrens (методов класса XML), откровенно говоря, неудобно. Так что вам остается либо использовать посторонние библиотеки (с нормальной поддержкой xpath) для разбора xml-документов либо перейти на actionscript3, где появилась наконец-то поддержка E4X. А что делать, если вы не можете перейти на новый язык? Скажем, если вы поддерживаете унаследованную программную систему, к которой периодически нужно добавлять новые функции. Когда вышел flash 9 с поддержкой actionscript 3 (flex builder, использующий ту же библиотеку классов и язык, был доступен еще год назад), то выбор стал гораздо шире. Я сам предпочитаю использовать для своих проектов AMFPHP- и SOAP-сервисы. Их можно использовать как в flash8, так и flash9 основанных проектах (в случае flash8 вам потребуется еще установить дополнительное расширение к flash — Flash remoting components). С другой стороны, я признаю, что для такого подхода требуются достаточно серьезные знания в области программирования, и большинство начинающих флэшеров это только отпугнет. В общем, знакомьтесь: SWX — простая, дружелюбная и хорошо документированная библиотека для организации удаленных вызовов, основанная на php, из основанных на actionscript2-проектов.

Домашний сайт проекта: сайт . На момент написания статьи последняя доступная версия была 1.0 beta. Но библиотека развивается достаточно быстро — новые версии выходят буквально каждые два-три месяца. Так, в перспективе разработчики хотят добавить поддержку и actionscript3-основанных проектов. После того, как вы скачали и распаковали архив с библиотекой, мы выполним настройку ее серверной части. Так как обычно серверные скрипты используются, очевидно, не для тривиальных расчетов (сколько будет a+b), а для выборки данных из некоторой СУБД, то конечной целью этой статьи будет показать, как можно разработать приложение, соединяющееся с СУБД mysql, делающее из нее некоторую выборку данных, отображающее эти данные в DataGrid. Мы также реализуем операции добавления и удаления записей из этой таблицы. Начнем мы с краткого описания идеи, лежащей в основе swx. Вы все умеете загружать в flash-ролик некоторый внешний ресурс с помощью LoadMovie. А что если загружаемый файл будет не статическим заранее созданным роликом, а динамически генерируемым с помощью php клипом, содержащим объявления некоторых переменных? Очевидно, что вы сможете к ним обращаться. Так вот, на стороне сервера вы создаете некоторое количество сервисов — это обычные php- классы, с методами. Все эти классы/методы следует зарегистрировать с помощью менеджера сервисов. После чего вы из flash-ролика можете делать удаленные вызовы, просто указав имя класса сервиса и имя метода, который нужно вызвать в его составе. Забудьте о тех ужасных строках "index.php?fio=bill&age=12&action=save", которые вам приходилось анализировать в вызываемом php-файле, когда использовался объект XML или LoadVars. Теперь все — отправка запроса на сервер, разбор запроса, вызов php-метода и кодирование результатов его работы — будет выполнено прозрачно. Вам будет казаться, что вы вызываете функции? находящиеся не на сервере, а в самом ролике.

Давайте займемся настройкой вызываемого сервиса. Для этого скопируйте содержимое подкаталога php из архива внутрь каталога swxserver на вашем веб-сервере. Если вы мечтаете о собственном веб-сервере, но не знаете, как его установить и настроить, то рекомендую выкачать из Интернет упаковку с джентльменским набором "apache+php+mysql". Например, denver, xampp, wamp. Все, что вам нужно сделать, — запустить инсталлятор, который выполнит все настройки самостоятельно. Приятная особенность xampp в том, что вы можете легко переключаться между используемыми версиями php (4-я или 5-я версия) с помощью одного нажатия мыши. Настоятельно прошу всегда, когда вы создаете некоторое веб-приложение, выполнять его тест под разными версиями php, если не уверены в том, какой у вас будет коммерческий хостинг. Теперь по адресу
httр://localhost/swxserver/swx.php находится менеджер сервисов. Откройте файл swx_config.php — это конфигурационный файл с настройками swx. Их не так уж и много. Посмотрите значение переменной $servicesPath — она указывает путь к каталогу, где будут размещены файлы/классы/сервисы. По умолчанию это подкаталог httр://localhost/swxserver/services. В том случае, если объем пересылаемых данных от сервера клиенту достаточно велик, имеет смысл включить архивирование на лету сгенерированного swf-файла. Измените для этого значение переменной $compressionLevel (возможные значения от 0 до 9 — соответственно "нет сжатия" — "максимальное сжатие"). Теперь я создам базу данных, в которой будет находиться одна таблица Users со следующими полями: id_user, fio, birthdate, sex. Для этого я запустил консоль-панель управления mysql (она находится в каталоге mysql/bin/mysql.exe) и создал в ней базу данных и таблицу, как показано на рис. 1. Следующий шаг — написание класса сервиса. В его составе будут три метода. Первый из них — listUsers. Этот метод не будет получать никаких параметров. Результатом его работы будет массив, содержащий список ассоциативных массивов с кодирующими каждую запись — в последующем эти данные будут загружены внутрь DataGrid с помощью flash. Второй метод — dropUser (id_user). Получив на вход номер человека, этот метод выполнит его удаление из базы данных. И, наконец, третий метод — addUser (fio, birthdate, sex) — будет выполнять добавление нового пользователя в таблицу базы данных. В случае, если запрошенную операцию выполнить не удастся, все три функции будут возвращать специальное значение false. Я сознательно не использую никаких специальных (нестандартных и очень удобных) библиотек для доступа к данным в php. Возможно, в будущем я напишу статью, посвященную adodb, pdo, propel. Пока же выполню подсоединение к серверу с помощью метода mysql_connect, затем выберу базу данных с помощью функции mysql_select_db. Прежде, чем делать запросы на выборку или внесение данных, мне необходимо будет выполнить переход сервера mysql в режим работы с нужной мне кодировкой. Для этого я применил метод mysql_query ('SET NAMES кодировка');. Так как swf-файлы хранят текст в формате utf8, то и кодировка подключения к базе данных должна быть таковой. Все эти действия по подключению к БД я делаю в конструкторе класса.

Теперь нам нужно подумать о методах отладки кода. Хорошая новость в том, что в состав SWX входит написанное на flex приложение, играющее роль отладчика. Каждый раз, когда вы делаете вызов, сведения о возвращенных данных помещаются в этот отладчик. Приложение-отладчик находится тут: "php\analyzer\analyzer.swf". Внешний вид его окна показан на рис. 2. Но это еще не все. Мы договорились, что, если вызов некоторого метода привел к тому, что была сгенерирована ошибка, то этот метод возвращает значение false. А как теперь узнать, какая именно это была ошибка? Для примера я решил показать, что SWX поддерживает работу с сессиями, так что, когда возникает ошибка, я помещаю текст сообщения об этой ошибке внутрь специальной переменной сессии lasterrormsg. Также в состав класса UserService я ввел особый метод getLastError, который возвращает текст сообщения последней произошедшей ошибки. Вот полный код примера файла userservice.php (не забудьте поместить его внутрь каталога
httр://localhost/swxserver/services):

<?php
// создаем класс сервиса
class UserService {
var $connection;
// в этой переменной будет храниться ссылка на подключение к базе данных
function UserService (){
if (! session_id()){session_start();}
$this->connection = mysql_connect('localhost', 'root', '');
if ($this->connection){
if ( !mysql_select_db('swx_users', $this->connection)){
$_SESSION ['lasterrormsg'] = 'Contructor: Cannot Select DB';}
else{
mysql_query('SET NAMES utf8', $this->connection);} }
else{ $_SESSION ['lasterrormsg'] = 'Contructor: No Connection To DB Server'; } }

function getLastError (){
return isset($_SESSION['lasterrormsg'])?$_SESSION['lasterrormsg']:'NO ERRORS';}

function listUsers (){
if (! $this->connection){
return false;}
$list = mysql_query('select * from users', $this->connection);
// если запрос не удался, то возвращаем специальное значение false
if (! $list) return false;
$rez = array ();
while (($row = mysql_fetch_assoc($list)) !== false){
$rez [] = $row;}
mysql_free_result($list);
return $rez;}

function dropUser ($user_id){
if (! $this->connection){
return false; }
if (! is_numeric($user_id)){
$_SESSION ['lasterrormsg'] = 'dropUser: User ID IsNot Integer:' . $user_id;
return false; }
if (! mysql_query('delete from users where id_user = ' . $user_id, $this->connection)){
$_SESSION ['lasterrormsg'] = 'addUser: Cannot Add User : ' . mysql_error($this->connection);
return false; }
return true; }

function addUser ($fio, $birthdate, $sex){
if (! $this->connection){
$_SESSION ['lasterrormsg'] = 'listUsers: No Connection To DB Server';
return false; }
$sex = ($sex != 0 && $sex != 1)?'0':$sex;
if (! mysql_query('insert into users set fio = "'.mysql_escape_string($fio).'", birthdate="'.mysql_escape_string($birthdate).'", sex = ' . $sex, $this->connection)){
$_SESSION ['lasterrormsg'] = 'addUser: Cannot Add User : ' . mysql_error($this->connection);
return false;}
return mysql_insert_id($this->connection); } }// class
?>

Теперь мы вернемся к flash, создадим новый документ в стиле actionscript2 и добавим в на stage компонент DataGrid — дайте ему имя gridUsers. Также необходимо добавить две кнопки с именами btnLoadUsers и btnDropUser — это соответственно кнопка загрузки списка пользователей и удаления одного из них. Ниже разместите три текстовых поля для ввода ФИО, даты рождения и пола, дайте им имена txtFIO, txtBirthDate, txtSex. И, наконец, разместите кнопку, выполняющую отправку запроса на добавление данных в базу: btnAddUser. Затем в actions для первого кадра выполните инициализацию подсистемы SWX. Прежде всего необходимо импортировать пакет org.swxformat. Для того, чтобы flash знал, где его искать, не забудьте добавить с помощью Edit -> Preferences -> ActionScript ссылку classpath на каталог, куда вы распаковали архив swx (исходники библиотеки находятся в подкаталоге flash, там же вы можете найти еще примеры работы с swx). После импорта библиотеки следует создать объект типа SWX и указать для него два основных параметра gateway и encoding. Второй из них должен содержать не название кодировки для обмена данными (не самое удачное название переменной), а способ HTTP вызова, т.е. метод GET или метод POST. Первый же параметр хитрее — он должен указывать полный "httр://" путь к файлу php, играющему роль менеджера зарегистрированных на веб-сервере классов-сервисов. Я рекомендую задавать данный параметр не в виде константы, а получать через param из внешнего html-файла, в который внедрен ваш ролик-приложение. Это повышает гибкость проекта и дает возможность легко переносить код с одного хостинга на другой. На этом все — подготовка клиента к удаленному вызову завершена. Остается только написать код, присоединяющий к кнопкам обработчики событий, которые, в свою очередь, делают удаленные вызовы. Обратите внимание, что после обработки запросов addUser и dropUser (если, конечно, они были успешны) выполняется запрос listUser для получения обновленного списка пользователей.

// создаем обработчики событий для каждой из кнопок
btnLoadUsers.addEventListener("click", LoadUsersHandler);
btnDropUser.addEventListener("click", DropUserHandler);
btnAddUser.addEventListener("click", AddUserHandler);

import org.swxformat.*;
var swx:SWX = new SWX ();
swx.gateway = " http://localhost/swxserver/swx.php";
swx.encoding = "POST";
swx.debug = true;

// функция, срабатывающая при нажатии на кнопку "Загрузить список людей"
function DropUserHandler(evt_obj:Object){
var callDetails:Object = {
serviceClass: "UserService",
// имя вызываемого класса
method: "dropUser",
// имя вызываемого метода
args: [_root.gridUsers.dataProvider[_root.gridUsers.focusedCell.itemIndex].id_user],
result: [this, resultHandlerOnDrop],
timeout: [this, timeoutHandler],
debug: true }
// теперь делаем удаленный вызов
swx.call(callDetails);}

// функция, срабатывающая при нажатии на кнопку "Загрузить список людей"
function AddUserHandler(evt_obj:Object){
var callDetails:Object = {
serviceClass: "UserService",
// имя вызываемого класса
method: "addUser",
// имя вызываемого метода
args: [_root.txtFIO.text, _root.txtBirthDate.text, _root.txtSex.text],
result: [this, resultHandlerOnAdd],
timeout: [this, timeoutHandler],
debug: true }
// теперь делаем удаленный вызов
swx.call(callDetails);}

// функция, срабатывающая при нажатии на кнопку "Загрузить список людей"
function LoadUsersHandler(evt_obj:Object){
var callDetails:Object = {
serviceClass: "UserService",
// имя вызываемого класса
method: "listUsers",
args: [],
result: [this, resultHandlerOnLoad],
timeout: [this, timeoutHandler],
debug: true }
swx.call(callDetails);}

// функция, срабатывающая при нажатии на кнопку "Загрузить список людей"
function getLastError(){
var callDetails:Object = {
serviceClass: "UserService",
method: "getLastError",
args: [],
result: [this, resultHandlerOnGetLastError],
timeout: [this, timeoutHandler],
debug: true }
swx.call(callDetails);}

// функция, вызываемая при получении ответа на запрос getLastError
function resultHandlerOnGetLastError(event:Object){
trace ('LastError: ' + event.result);}

// функция, вызываемая при получении ответа на запрос addUser
function resultHandlerOnAdd(event:Object){
if ((''+event.result) == 'false')
getLastError ();
else{ LoadUsersHandler (event); }}

// а эта функция вызывается тогда, когда приходит ответ на запрос dropUser
function resultHandlerOnDrop(event:Object){
if ((''+event.result) == 'false')
getLastError ();
else{ LoadUsersHandler (event); }}

// обработчик события загрузка списка всех людей завершена
function resultHandlerOnLoad(event:Object){
if ((''+event.result) == 'false')
getLastError ();
else{ var x = new Array ();
for (var i = 0; i < event.result.length; i++)
x.push (event.result [i]);
_root.gridUsers.dataProvider = x;}}

// функция — обработчик события время ожидания исчерпано
function timeoutHandler(event:Object){
trace ("TimeOut");}

Результат работы скрипта показан на рис. 3. Вы можете доработать данный пример, добавив в него новые функции — например, редактирование данных, поиск, фильтры, сортировку.

black zorro black-zorro@tut.by

© Компьютерная газета

полезные ссылки
Аренда ноутбуков