Как сделать SOAP-вызов PHP с использованием класса SoapClient. Практический пример soap web-службы на php Пишем SOAP клиент-серверное приложение на PHP

SOAP (Simple Object Access Protocol) представляет из себя основанный на XML протокол, предназначенный для обмена структурированной информацией между распределенными приложениями поверх существующих в веб протоколов, например HTTP. Спецификация SOAP определяет формат, используемый XML-сообщениями, то, как они должны обрабатываться набор правил кодирования для стандарта, типы данных а также соглашения для вызова удаленных процедур и ответы на вызовы.

Веб-сервисы - это модная и современная технология. Список технологий, относящихся к веб-сервисам увеличивается практически ежедневно, но SOAP является, вероятно, наиболее важной из них. Он стремительно становится стандартным протоколом доступа к веб-сервисам. Он использует XML-сообщения для обмена информацией между конечными точками, и в тоже время предоставляет некоторые преимущества бинарных протоколов. Поддержка RPC (Remote Procedure Calls) в начале была одной из незначительных возможностей протокола SOAP, но сейчас она превратилась в одну из наиболее часто используемых возможностей.

SOAP-расширение для PHP 5 - это первая попытка организовать поддержку SOAP в PHP на Си. У нее есть несколько преимуществ перед существующими реализациями SOAP, написанными на PHP, и самое важное из них - скорость. В данный момент расширение считается экспериментальным, но постепенно оно будет становиться все боле надежным и стабильным.

В расширении SOAP реализованы большие подмножества спецификаций SOAP 1.1, SOAP 1.2 и WSDL 1.1. Главная цель - максимально использовать RPC-возможности SOAP. Везде, где это возможно используется WSDL, чтобы сделать реализацию веб-сервисов более простой.

Первый SOAP-клиент

Чтобы продемонстрировать создание простого SOAP-клиента используем демонстрационный сервис "Delayed Stock Quote" с сайта XMethods. Перед тем как мы начнем писать PHP-код, необходимо собрать следующую информацию о данном конкретном сервисе:

  • Имя метода
  • URL по которому расположен этот сервис
  • Значение заголовка SOAPAction метода
  • Пространство имен метода
  • Имена и типы входных и выходных параметров метода

К счастью, вся эта информация доступна на сайте XMethods по адресу http://www.xmethods.com/ из RPC-профиля сервиса:

Имя метода getQuote
URL сервиса http://66.28.98.121:9090/soap
SOAPAction urn:xmethods-delayed-quotes#getQuote
Пространство имен метода urn:xmethods-delayed-quotes
Входные параметры Symbol (String)
Выходные параметры Result (float)

Пример 1 (client1.php)

$client = new SoapClient (NULL ,
array(
"location" => "http://66.28.98.121:9090/soap" ,
"uri" =>
"style" => SOAP_RPC ,
"use" => SOAP_ENCODED
));

Print($client -> __call (
/* Имя SOAP-метода */
"getQuote" ,
/* Параметры */
array(
new SoapParam (
/* Значение параметра */
"ibm" ,
/* Имя параметра */
"symbol"
)),
/* Опции */
array(
/* Пространство имен SOAP-метода */
"uri" => "urn:xmethods-delayed-quotes" ,
/* HTTP-заголовок SOAPAction для SOAP-метода */
"soapaction" => "urn:xmethods-delayed-quotes#getQuote"
)). "\n" );
?>

Как видите, решение этой простой задачи потребовало довольно много работы.

К счастью веб-сервисы могут описывать себя клиентам с помощью WSDL, в целом это довольно удобно. WSDL для сервиса "Delayed Stock Quote" представлен на его информационной страничке на сайте xmethods.com - http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl .

Вот вариант этого же клиента, переписанный для работы с помощью этого WSDL-документа. Здесь нам уже не нужно указывать URI-сервера, пространство имен, заголовок SOAPAction, способ кодирования и типы параметров. Вся эта информация берется из WSDL файла.

Пример 2 (client2.php)

$client = new
SoapClient (
"http://services.xmethods.net/soap/urn:xmethods-delayed-quotes.wsdl"
);

Print($client -> getQuote ("ibm" ));
?>

Так несколько проще, правда?

Какие проблемы возникают при использовании WSDL? Единственный аргумент против его использования состоит в том, что клиент должен прочитать WSDL с сервера до того, как можно будет вызвать какую-нибудь процедуру, а в веб это может занять довольно много времени. Для того, чтобы ускорить работу в SOAP-расширении предусмотрены следующие параметры конфигурации: soap.wsdl_cache_enabled, soap.wsdl_cache_dir and soap.wsdl_cache_ttl. Их можно задать в файле php.ini или с помощью ini_set()(см. Пример 4). По умолчанию кэширование WSDL включено и WSDL-файлы кэшируются на 1 день.

Вот раздел SOAP файла php.ini с значениями по умолчанию. Вы можете скопировать их в свой php.ini.

[ soap ]

Soap . wsdl_cache_enabled = "1"
; включает или выключает кэширование WSDL

Soap . wsdl_cache_dir = "/tmp"
; задает имя директории в которой SOAP - расширение будет хранить кэшированные файлы

Soap . wsdl_cache_ttl = "86400"
; (время жизни ) устанавливает время (в секундах ) которое файлы из кэша могут использоваться

Первый SOAP-сервер

Попробуем написать собственный SOAP веб-сервис, который будет делать тоже, что и сервис "Delayed Stock Quote" с XMethods.

Первое, что нужно сделать - это создать WSDL-документ, описывающая наш сервис в формате, понятном клиентам. Для этого понадобится несколько изменить взятый с сайта Xmethods оригинальный документ, потому начнем мы с того, что рассмотрим его более подробно.

Раздел message определяет два сообщения. Первое - getQuoteRequest, которое передает сообщение getQuote и принимает одностроковый параметр с именем symbol. Второе сообщение - getQuoteResponse, ответ на запрос getQuote, передающий одно значение типа float с именем Result.

Раздел portType определяет единственную операцию getQuote, которая указывает, какое из описанных в разделе message будет использоваться для запроса, а какое для ответа.

В разделе binding определяется как сообщение должно кодироваться и передаваться. В данном случае в нем указано, что мы пошлем RPC-запрос через HTTP, используя SOAP-кодирование. Здесь также определены пространство имен и значение заголовка SOAPAction для метода getQuote.

И наконец в разделе service определяется URL по которому находится сервис.

Пример 3 (stockquote.wsdl)


targetNamespace="http://example.org/StockQuote"
xmlns:tns=" http://example.org/StockQuote "
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns="http://schemas.xmlsoap.org/wsdl/">












transport="http://schemas.xmlsoap.org/soap/http"/>



encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>


encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>








Замечание: по умолчанию кэширование WSDL включено. На время разработки и отладки вашего WSDL, кэширование лучше отключить.

Теперь самое время приступить к созданию нашего сервера.

Прежде всего мы разработаем функцию getQuote(), которая будет обрабатывать входящие запросы из веб. Далее мы создадим объект класса SoapServer и присоединим к нему нашу функцию с помощью метода SoapServer::addFunction(). Как вы увидите в дальнейшем, у конструктора SoapServer() есть только один параметр - путь к WSDL-документу, описывающему сервис.

Пример 4 (server1. php)

$quotes = array(
"ibm" => 98.42
);


global $quotes ;
return $quotes [ $symbol ];
}

Ini_set ("soap.wsdl_cache_enabled" , "0" ); // отключаем кэширование WSDL
$server = new SoapServer ("stockquote1.wsdl" );
$server -> addFunction ("getQuote" );
$server -> handle ();
?>

SoapServer может работать и без WSDL, примерно также как клиент, но у такого варианта нет никаких преимуществ, из-за которых стоило бы его использовать. Если вы все же хотите работать именно так, вы должны убедиться что возвращаемые значения - это объекты классов SoapParam и SoapVar(как в первом примере.

Вот клиент для доступа к нашему SOAP-серверу. По сравнению с предыдущим примером добавилась только ссылка на местонахождение WSDL. Предполагается, что файл "stockquote1.wsdl" лежит в той же директории что и SOAP-сервер.

Пример 5 (client3.php)

$client = new SoapClient ("stockquote1.wsdl" );
print($client -> getQuote ("ibm" ));
?>

Какие основные проблемы есть в наших клиенте и сервере?

Для начала, они не обрабатывают ошибки. Что происходит, когда сервер не находит подходящий результат для переданного ему значения переменной symbol? В SOAP существует специальный формат сообщений для сообщений об ошибках - SoapFault.Чтобы сгенерировать такое сообщение, сервер должен вызвать исключение с помощью объекта SoapFault. Первый параметр конструктора SoapFault() это строка с кодом ошибки, второй - строка с описанием ошибки. Клиент должен быть написан так, чтобы обрабатывать исключения SoapFault.

Во вторых, функциональность веб-сервиса лучше инкапсулировать в PHP-класс. В этом случае нам не нужно будет использовать глобальные переменные и добавлять к серверу каждый SOAP-метод по отдельности. Вместо этого мы сможем добавить класс целиком, и все его методы станут доступны через SOAP. Вот соответствующим образом модифицированные версии клиента и сервера.

Пример 6 (server2.php)

class QuoteService {
private $quotes = array("ibm" => 98.42 );

Function getQuote ($symbol ) {
if (isset($this -> quotes [ $symbol ])) {
return $this -> quotes [ $symbol ];
} else {
throw new
SoapFault ("Server" , "Unknown Symbol "$symbol"." );
}
}
}

$server = new SoapServer ("stockquote2.wsdl" );
$server -> setClass ("QuoteService" );
$server -> handle ();
?>

Как видите, я использовал метод SoapServer::setClass() для соединения объекта SoapServer с классом QuoteService.

Пример 7 (client4.php)

$client = new SoapClient ("stockquote2.wsdl" );
try {
echo "

\n"
; 
print($client -> getQuote ("ibm" ));
echo "\n" ;
print($client -> getQuote ("microsoft" ));
echo "\n
\n" ;
} catch (SoapFault $exception ) {
echo $exception ;
}
?>

А что внутри?

Если вы хотите разобраться в формате SOAPсообщений, или хотите самостоятельно отлаживать SOAP-клиента, то этот раздел для вас.

Как вы видели в первом примере, конструктор SoapClient() принимает ассоциативный массив в качестве второго параметра. С помощью этого массива мы можем вызывать различные опции на сервере.

Посмотрим две из них:

  • trace - позволяет клиенту сохранять SOAP-запросы и ответы (по умолчанию выключено).
  • exceptions - позволяет клиенту контролировать механизм исключений (по умолчанию включено).

Посмотрим на следующий пример SOAP-клиента. Это доработанный клиент из Примера 5, в точности показывающий, что передается между клиентом и сервером. Для получения этой информации используются методы __getLastRequest() и __getLastResponse().

Пример 8 (client5.php)

$client = new SoapClient ("stockquote1.wsdl" ,array(
"trace" => 1 ,
"exceptions" => 0 ));
$client -> getQuote ("ibm" );
print "

\n"
; 
print "Запрос:\n" . htmlspecialchars ($client -> __getLastRequest ()) . "\n" ;
print "Ответ:\n" . htmlspecialchars ($client -> __getLastResponse ()). "\n" ;
print "
" ;
?>

Вот вывод скрипта. Он немного изменен, для упрощения понимания.

Запрос:



xmlns:xsd="http://www.w3.org/2001/XMLSchema"

SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">


ibm


Ответ:


xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:ns1="urn:xmethods-delayed-quotes"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">


98.42


Другие реализации SOAP для PHP

Все они написаны на PHP, а не на Си.

Резюме

В этой статье я описал только основные функции SOAP-расширения. На самом деле оно может гораздо больше, но продемонстрировать все его возможности в рамках одной короткой статьи попросту невозможно. Вот список главных из них:

  1. Поддержка комплексных типов данных (массивов, объектов)
  2. Поддержка SOAP - заголовков
  3. Динамическая поддержка SOAP 1.1 и SOAP 1.2

Возможно они будут более подробно рассмотрены в последующих статьях.

Подробная документация по SOAP-расширению расположена по адресу http://www.php.net/manual/en/ref.soap.php .

Разработка этого расширения находится на начальном этапе, поэтому ваши отзывы помогут сделать его более стабильным, надежным, удобным и быстрым. Пожалуйста сообщайте о всех возникающих при его использовании проблемах по адресу http://bugs.php.net/ .

Ссылки

Об авторе

Я привык писать PHP-код, но не часто использую объектно-ориентированное кодирование. Теперь мне нужно взаимодействовать с SOAP (как клиент), и я не могу правильно получить синтаксис. У меня есть файл WSDL, который позволяет мне правильно настроить новое соединение с использованием класса SoapClient. Тем не менее, я не могу сделать правильный звонок и вернуть данные. Мне нужно отправить следующие (упрощенные) данные:

  • Contact ID
  • Контактное имя
  • Общее описание
  • Сумма

В документе WSDL есть две функции, но мне нужен только один ("FirstFunction" ниже). Вот script, который я запускаю, чтобы получить информацию о доступных функциях и типах:

$client = new SoapClient("http://example.com/webservices?wsdl"); var_dump($client->__getFunctions()); var_dump($client->__getTypes());

И вот результат, который он генерирует:

Array( => "FirstFunction Function1(FirstFunction $parameters)", => "SecondFunction Function2(SecondFunction $parameters)",); array( => struct Contact { id id; name name; } => string "string description" => string "int amount" }

Предположим, что я хочу позвонить FirstFunction с данными:

  • Contact ID: 100
  • Контактное лицо: John
  • Общее описание: Barrel of Oil
  • Сумма: 500

Каким будет правильный синтаксис? Я пробовал всевозможные варианты, но кажется, что мыльная структура довольно гибкая, поэтому есть очень много способов сделать это. Не удалось понять это из руководства...

ОБНОВЛЕНИЕ 1: пробованный образец из MMK:

$client = new SoapClient("http://example.com/webservices?wsdl"); $params = array("id" => 100, "name" => "John", "description" => "Barrel of Oil", "amount" => 500,); $response = $client->__soapCall("Function1", array($params));

Но я получаю этот ответ: Object has no "Contact" property . Как вы можете видеть на выходе getTypes() , существует struct , называемый Contact , поэтому, я думаю, мне почему-то нужно уточнить, что мои параметры включают данные Contact, но вопрос в следующем: как?

ОБНОВЛЕНИЕ 2: Я также пробовал эти структуры, такую ​​же ошибку.

$params = array(array("id" => 100, "name" => "John",), "Barrel of Oil", 500,);

Также как:

$params = array("Contact" => array("id" => 100, "name" => "John",), "description" => "Barrel of Oil", "amount" => 500,);

Ошибка в обоих случаях: объект не имеет свойства "Контакт"

8 ответов

Это то, что вам нужно сделать.

Просто чтобы знать, я попытался воссоздать вашу ситуацию...

  • В этом примере я создал веб-сервис.NET с помощью WebMethod под названием Function1 , и это параметры:

Функция1 (контактный контакт, описание строки, количество int)

    В котором Contact есть только bean class , который имеет getters и seters для id и name , как в вашем случае.

    Вы можете загрузить этот веб-сервис.NET с помощью:

Код.

Это то, что вам нужно сделать на стороне PHP :

(Протестировано и работает)

id = $id; $this->name = $name; } } /* Initialize webservice with your WSDL */ $client = new SoapClient("http://localhost:10139/Service1.asmx?wsdl"); /* Fill your Contact Object */ $contact = new Contact(100, "John"); /* Set your parameters for the request */ $params = array("Contact" => $contact, "description" => "Barrel of Oil", "amount" => 500,); /* Invoke webservice method with your parameters, in this case: Function1 */ $response = $client->__soapCall("Function1", array($params)); /* Print webservice response */ var_dump($response); ?>

Как я знаю, что это работает?

  • Если вы сделаете print_r($params); , вы увидите этот вывод, поскольку ваш веб-сервис ожидает:

Массив ([Контакт] = > Контактный объект ( = > 100 = > John) [описание] = > Бочка с маслом [количество] = > 500)

  • Когда я отлаживал образец.NET webservice, я получил следующее:

(Как вы видите, Contact объект не является нулевым, а также другими параметрами, это означает, что ваш запрос был успешно выполнен с PHP-стороны).

  • Ответ от.NET webservice был ожидаемым и показан на стороне PHP:

object (stdClass) public "Function1Result" = > string "Подробный информация по вашему запросу! id: 100, имя: John, описание: Barrel масла, количество: 500 "(длина = 98)

Надеюсь, что это поможет: -)

Вы также можете использовать SOAP-сервисы:

"Spain", "CityName" => "Alicante"); $response = $soapclient->getWeather($params); var_dump($response); // Get the Cities By Country $param = array("CountryName" => "Spain"); $response = $soapclient->getCitiesByCountry($param); var_dump($response);

Это пример с реальным сервисом, и он работает.

Надеюсь, что это поможет.

Сначала выполните инициализацию веб-сервисов:

$client = new SoapClient("http://example.com/webservices?wsdl");

Затем установите и передайте параметры:

$params = array ("arg0" => $contactid, "arg1" => $desc, "arg2" => $contactname); $response = $client->__soapCall("methodname", array($params));

Обратите внимание, что имя метода доступно в WSDL в качестве имени операции, например:

Я не знаю, почему мой веб-сервис имеет одинаковую структуру с вами, но ему не нужен класс для параметра, просто массив.

Например: - Мой WSDL:

5390a7006cee11e0ae3e0800200c9a66 831f8c1ad25e1dc89cf2d8f23d2af...fa85155f5c67627 VITS-STAELENS Zoethout thee 0.100 10K24 2012-12-31 Gladys Roldan de Moras

Calle General Oraá 26 (4º izda) 28006 Madrid ES
[email protected] es

Var_dump($client->getFunctions()); var_dump($client->getTypes());

Вот результат:

Array 0 => string "OrderConfirmation createOrder(OrderRequest $createOrder)" (length=56) array 0 => string "struct OrderRequest { Identification identification; Delivery delivery; Parcel parcel; Receiver receiver; string reference; }" (length=130) 1 => string "struct Identification { string sender; string hash; string originator; }" (length=75) 2 => string "struct Delivery { Node from; Node to; }" (length=41) 3 => string "struct Node { string country; string node; }" (length=46) 4 => string "struct Parcel { string description; decimal weight; string orderNumber; date orderDate; }" (length=93) 5 => string "struct Receiver { string firstName; string surname; Address address; string email; string language; }" (length=106) 6 => string "struct Address { string line1; string line2; string postalCode; string city; string country; }" (length=99) 7 => string "struct OrderConfirmation { string trackingNumber; string reference; }" (length=71) 8 => string "struct OrderServiceException { string code; OrderServiceException faultInfo; string message; }" (length=97)

Итак, в моем коде:

$client = new SoapClient("http://packandship-ws.kiala.com/psws/order?wsdl"); $params = array("reference" => $orderId, "identification" => array("sender" => param("kiala", "sender_id"), "hash" => hash("sha512", $orderId . param("kiala", "sender_id") . param("kiala", "password")), "originator" => null,), "delivery" => array("from" => array("country" => "es", "node" => "",), "to" => array("country" => "es", "node" => "0299"),), "parcel" => array("description" => "Description", "weight" => 0.200, "orderNumber" => $orderId, "orderDate" => date("Y-m-d")), "receiver" => array("firstName" => "Customer First Name", "surname" => "Customer Sur Name", "address" => array("line1" => "Line 1 Adress", "line2" => "Line 2 Adress", "postalCode" => 28006, "city" => "Madrid", "country" => "es",), "email" => "[email protected]", "language" => "es")); $result = $client->createOrder($params); var_dump($result);

но он успешно!

Это хороший пример для функции SOAP "__call". Однако он устарел.

Envio Internacional: "; $vem = $cliente->__call("CustoEMSInternacional",array($int_zona, $int_peso)); print $vem; print "

"; ?>

In this example we will see an example of a SOAP client communicating with a server. SOAP is one of many web service protocol definition. A web service is a technology that uses a protocol (SOAP in this example) for exchanging data between applications over the network.

If we are developing a client for consuming a web service, we have to look at the protocol that the service is using. This example is for looking at the resources provided by PHP for developing a client for a web service implementing the SOAP protocol.

For this example, we will use:

  • Ubuntu (14.04) as Operating System.
  • Apache HTTP server (2.4.7).
  • PHP (5.5.9).

1. Preparing the environment

1.1. Installation

Below, commands to install Apache and PHP are shown:

Sudo apt-get update sudo apt-get install apache2 php5 libapache2-mod-php5 sudo service apache2 restart

1.2. PHP configuration

Even if it’s not necessary, is recommendable to disable the SOAP WSDL cache for development environments. In /etc/php5/apache2/php.ini file, set the soap.wsdl_cache_enabled directive to 0:

php.ini

Soap.wsdl_cache_enabled=0

Don’t forget to restart Apache after doing any change.

Note: if you want to use Windows, installing XAMPP is the fastest and easiest way to install a complete web server that meets the prerequisites.

2. What is SOAP?

SOAP (Simple Object Access Protocol) is a standard protocol that defines how two different objects in different processes can communicate each other, through data exchange in XML format. Is one of the widest protocols used in web services.

In other words, this protocol allows to call method of objects that are defined in remote machines.

3. PHP example of SOAP clients

The server offering their SOAP services can define them in two different ways:

  • With WSDL (Web Services Definition Language)
  • Without WSDL

From the client point of view, there are very few differences, but let’s see how to proceed for each one.

We will consider the following scenario, which is quite typical: a web service that inserts and reads from a database (simulated with a plain text file), for which we will develop a client.

Is responsibility of the server offering the service to let know the protocol used, as same as the available method definitions, in order to let the clients know how to deal with the service.

Even if this tutorial is about the client itself, we will see also the server side, in order to test that the client is actually working.

3.1. Working directory structure

For this example, we will use the following structure:

Php_soapclient_example/ ├── simple_soap_client_class.php ├── simple_soap_server_class.php ├── handle_soap_request.php ├── no_wsdl │ └── server_endpoint.php └── wsdl ├── server_endpoint.php └── simple_service_definition.wsdl

Where the root directory, php_soapclient_example , will be in the web server’s root directory, which is /var/www/html/ by default.

Briefly explained each file:

  • simple_soap_client_class.php is the class defined for the SOAP client methods.
  • simple_soap_server_class.php is the class defined for the SOAP server methods.
  • handle_soap_request.php is for instantiate and use the SimpleSoapClient class defined in simple_soap_client_class.php .
  • In the directories wsdl and no_wsdl , the service defined in simple_soap_server_class.php is offered, in the way it accords to each of the modes. We won’t go into details for these, since it is not the purpose of this example. The only thing we have to know is that in one of the directories there is a service offered in WSDL mode, and that in the other, in no WSDL mode; they are two independent web services that use the code defined in simple_soap_server_class.php .

Note: the WSDL mode needs a .wsdl file, where the web service is defined. We are not going to see it in this example, since the aim is not to see how to build a WSDL definition file, but it’s also available to download in the last section of the example.

3.2. The server

As said, our server will read and write data into a text file. Let’s see it:

simple_soap_server_class.php

", FILE_APPEND); if ($writtenBytes) { $response = "$writtenBytes bytes have been inserted."; } else { $response = "Error inserted data."; } return $response; } /** * Reads data. Invoked remotely from SOAP client. * * @return Data of file. */ public function readData() { $contents = file_get_contents(self::FILENAME); return $contents; } }

So simple, about reading and writing into a text file.

Note the functions this server implements, in lines 16 and 33. This functions will be those invoked by the client.

3.3. The client

As we said above, depending of the mode (WSDL or not WSDL), the client has to handle the connection in a different way but, once stablished, the procedure is the same. So, let’s code a class that works for both modes, handling the SoapClient class instantiation accordingly to the mode:

simple_soap_client_class.php

initializeClient($soapMode, $serverLocation); } /** * Instantiates the SoapClient, depending on the specified mode. * * For WSDL, it just has to be instantiated with the location of the service, which actually has to be the * .wsdl location. * * For non-WSDL, the first parameter of the constructor has to be null; and the second, an array specifying * both location and URI (which can be the same, the important parameter is the location). */ protected function initializeClient($soapMode, $serverLocation) { switch ($soapMode) { case self::MODE_WSDL: $this->client = new SoapClient($serverLocation); break; case self::MODE_NO_WSDL: $options = array("location" => $serverLocation, "uri" => $serverLocation); $this->client = new SoapClient(NULL, $options); break; default: throw new Exception("Error: invalid SOAP mode provided."); break; } } /** * Inserts data remotely into the SOAP service. * * @param $data Data to insert remotely. * @return Response from remote service. */ public function insertData($data) { $response = $this->client->insertData($data); return $response; } /** * Reads data from SOAP service. * * @return Data received from remote service. */ public function readData() { return $this->client->readData(); } }

The lines 35 and 45 instantiate the SoapClient class, depending on the $mode received. This is how it works for each mode:

  • For WSDL mode, we have just to pass the server location to the constructor. For this mode, the location must be the WSDL definition file (.wsdl), not a PHP file .
  • For non WSDL mode, the first parameter has to be null (because we are not accessing a WSDL definition). So, the location must be defined in a different way, providing an array with "location" and "uri" elements, with the server location as values. In this case, the location must be the PHP file handling the web service .

After the instantiation, the communication with the service is pretty simple. We have just to call the methods that we saw defined in SimpleSoapServer class, in simple_soap_client_class.php , through the SoapClient class instance. As we said before, we are calling methods that are defined in other place. What PHP SoapClient does, is to provide us those methods defined by the web service, and, when we call them, it will execute them in the server through the SOAP protocol that it has already implemented, with no needing to care about how it works. Seems magic, doesn’t it?

If you don’t believe it, let’s see a script to use this client.

3.4. Using the client

The following script allows us to use the client to communicate with the service, through GET parameters. These are the parameters available:

  • "mode" , to specify the mode (WSDL or non WSDL).
  • "action" , to specify the action to perform. Available values are "insert" and "read" .
  • "value" , to specify the value to insert, only necessary when the action is "insert" .

handle_soap_request.php

getMessage()); } return $soapClient; } // Flow starts here. checkGETParametersOrDie(["mode", "action"]); $mode = $_GET["mode"]; $action = $_GET["action"]; $soapClient = instantiateSoapClient($mode); switch($action) { case ACTION_INSERT: checkGETParametersOrDie(); $value = $_GET; try { $response = $soapClient->insertData($value); echo "Response from SOAP service: $response
"; } catch (Exception $exception) { die("Error inserting into SOAP service: " . $exception->getMessage()); } break; case ACTION_READ: try { $data = $soapClient->readData(); echo "Received data from SOAP service:
"; echo $data; } catch (Exception $exception) { die("Error reading from SOAP service: " . $exception->getMessage()); } break; default: die("Invalid "action" specified."); break; }

If we enter in the browser http://127.0.0.1/php_soapclient_example/handle_soap_request.php?mode=no_wsdl&action=insert&value=testing_no_wsdl , the service will create a no_wsdl/data.txt file (if not exists already), writing the provided value "testing_no_wsdl" , and the following will be printed:

Response from SOAP service: 19 bytes have been inserted.

(The 4 bytes extra correspond to additional
characters inserted into the file).

The service offers a method to reading all the data, so, if we enter in the browser http://127.0.0.1/php_soapclient_example/handle_soap_request.php?mode=no_wsdl&action=read , the following will be printed:

Received data from SOAP service:
testing_no_wsdl

We can check it also for WSDL mode, setting mode=wsdl in the parameters.

4. Considerations

As said in section 3, when we are developing a web service client (no matter if SOAP, REST, etc.), the service provider has to provide also documentation about the available methods; we need to know the definition of these: which parameters is expecting, af if it returns something. Because of that, the most usual way to develop a SOAP web service is using WSDL files, to provide documentation of the available methods. In any case, the SoapClient has a method to get the available methods, for debugging purposes, named __getFunctions() . So, we could see the offered methods by the service with the following:

Var_dump($soapClient->__getFunctions())

Assuming that $soapClient has been instanced properly.

Also note that, if the service does not offer an encrypted connection, the communication between the client and the server will be made in plain text. So, if we have to develop a client that has to deal with sensible information, we should ensure that the communication with the server can be considered safe.

5. Summary

We have seen how to develop a client for a SOAP web service using PHP’s SoapClient class. The server may offer the service using WSDL, or not, something to take into account when we are instantiating the SoapClient class. Then, to use the methods provided by the service, we just have to call those methods in SoapClient instance, as they were ours.

Для создания SOAP web-службы на PHP прежде всего необходимо провести установку и настройку PHP, а также локального HTTP-сервера. В данном проекте был использован пакет XAMP, позволяющий провести быструю установку HTTP-сервера Apache и PHP.

Отдельно необходимо внести правки в конфигурационные файлы Apache и PHP.

В конфигурационном файле HTTP-сервера Apache httpd.conf необходимо найти приведенный ниже фрагмент кода:

Для запуска PHP-скриптов на локальном сервере необходимо хранить их в корневой директории сервера Apache, по умолчанию, папка htdocs в корневой папке Apache.

Для изменения корневой директории сайта необходимо внести изменения в файл httpd.conf в строку, приведенную ниже:

Для подключения поддержки PHP в файл httpd.conf необходимо внести следующие строки:


Для запуска HTTP-сервера Apache можно использовать консольные команды, либо контрольную панель XAMMP, позволяющую управлять состоянием Apache, а также проводить его конфигурацию.

Вид данной утилиты приведен на рисунке 2.

Рисунок 2. - Утилита XAMMP Control Panel:


Для создания SOAP web-службы необходимо написать PHP документы, реализующие клиентскую и серверную стороны приложения, а также WSDL-страницу, для описания web-сервиса.

Создадим web-службу, которая предоставляет одну web-услугу, возвращающую клиенту результат выполнения арифметической операции над двумя числами. Входными элементами являются два числа, задаваемые клиентом, а также символьная переменная, определяющая арифметическую операцию.

Клиентская сторона реализована двумя PHP-документами. В файле form.php при помощи элемента

описана форма, в которую пользователь вводит данные. В свойствах формы прописан метод POST, применяемый для извлечения информации из формы и передачи ее в PHP-файл. В данном случае происходит передача введенных пользователем данных в afqk code.php. Элементы описывают текстовые поля для ввода данных и кнопку, при нажатии которой происходит передача данных в файл code.php.

Фрагмент кода, реализующий файл form.php, приведен ниже:


В файле code.php создается экземпляр класса клиента и происходит получение введенных пользователем данных и их отсылка в SOAP послании серверу. Фрагмент кода, реализующий создание экземпляра класса клиента приведен ниже:

В данном фрагменте создается экземпляр $client класса SoapClient, выполняющий SOAP-послание в соответствие с конфигурационным файлом cacl.wsdl.

В файле code.php также выполняется присвоение переменным $a, $b, $action значений членов ассоциативного массива $_POST, хранящего полученные из form.php данные.

Фрагмент кода, реализующий данные операции приведен ниже:

Где getCalcEntry() - описанная в WSDL-файле операция.

В данной web-службе роль серверной части играет файл calc-server.php.

В данном файле происходит объявление и вызов функции getCalcEntry.

Данная функция проверяет значение переменной $action, и в соответствии с ним выполняет операцию над переменными $a и $b, записывая результат в переменную $c. В данном фрагменте метод addFunction() предоставляет доступ к функции getCalcentry удаленному клиенту. Метод handle() выполянет обработку SOAP-запроса, вызов необходимых функций и посылку SOAP-ответа. Фрагмент кода, реализующий данную функцию, приведен ниже:


Важную роль в работе данной SOAP web-службы играет файл описания calc.wsdl.

В данном файле определены основные параметры SOAP-посланий. Фрагмент кода, отвечающий за реализацию этого, приведен ниже:


В данном фрагменте элементы getCalcRequest и getCalcResponse - SOAP-запрос и SOAP-ответ соответственно. В вложенных в элементах определены имена передаваемых переменных и их типы. Следует заметить, что возможна передача строк (тип string), которые будут преобразованы в числа неявно, благодаря динамической типизации в PHP. Также важным элементом WSDL-файла является описание интерфейса web-службы, выполняемое элементом

В данном фрагменте элемент описывает конкретную услугу getCalcEntry, состоящую из двух действий:

  • - первое действие getCalcRequest представляет отправку послания;
  • - второе действие getCalcResponse - получение ответа.

Для успешной работы web-сервиса на локальном сервере необходимо:

  • 1) Создать отдельную директорию для распределенного приложения в корневой директории сервера Apache. Поместить в нее файлы form.php, code.php, calc-server.php и calc.wsdl;
  • 2) Запустить Apache, используя консоль либо контрольную панель XAMMP;
  • 3) В адресной строке браузера ввести адрес клиента web-сервиса;
  • 4) Ввести данные в текстовые поля и нажать кнопку Calculate;
  • 5) В результате во вкладке браузера откроется PHP-страница с выведенным результатом вычислений.

Рисунок 3. - Результат работы модифицированной web-службы:


Для того, чтобы более подробно рассмотреть работу SOAP, модифицируем код файла code.php таким образом, что в браузер вместе с результатом вычисления будут выводиться листинги последних SOAP-посланий - запроса и ответа.

Фрагмент кода, выполняющий данные функции приведен ниже:


программирование web сервер

Данный фрагмент содержит стандартные системные методы класса:

Возвращающие текст SOAP-посланий. При рассмотрении полученных таким образом SOAP-посланий, явно виден обмен данными между клиентом и сервером. В случае запроса данные содержатся в приведенном ниже фрагменте кода. Данный фрагмент содержит тело SOAP-конверта, в которое вложен элемент , описывающий web-услугу.


В него вложены элементы, хранящие значения передаваемых переменных и их типы данных. Серверу передаются значения чисел 5 и 3, а также символ выполняемой над ними операции вычитания.

Аналогичный фрагмент кода для ответа приведен ниже:


В данном фрагменте кода видно передаваемое клиенту значение результата вычитания - 2.

hats 23 июля 2013 в 13:09

Пишем SOAP клиент-серверное приложение на PHP

  • PHP
  • Tutorial

Всем привет!
Так случилось, что в последнее время я стал заниматься разработкой веб-сервисов. Но сегодня топик не обо мне, а о том, как нам написать свой XML Web Service основанный на протоколе SOAP 1.2.

Я надеюсь, что после прочтения топика вы сможете самостоятельно:

  • написать свою собственную серверную реализацию веб-приложения;
  • написать свою собственную клиентскую реализацию веб-приложения;
  • написать свое собственное описание веб-сервиса (WSDL);
  • отправлять клиентом массивы однотипных данных на сервер.
Как вы могли догадаться, вся магия будет твориться с использованием PHP и встроенных классов SoapClient и SoapServer. В качестве кролика у нас будет выступать сервис по отправке sms-сообщений.

1 Постановка задачи

1.1 Границы

В начале предлагаю разобраться с тем результатом, которого мы достигнем в конце топика. Как было объявлено выше, мы будем писать сервис по отправке sms-сообщений, а если еще точнее, то к нам будут поступать сообщения из разных источников по протоколу SOAP. После чего, мы рассматрим в каком виде они приходят на сервер. Сам процесс постановки сообщений в очередь для их дальнейшей отправки провайдеру, к сожалению, выходит за рамки данного поста по многим причинам.

1.2 Какими данными будем меняться?

Отлично, с границами мы определились! Следующий шаг, который необходимо сделать – решить какими данными мы будем обмениваться между сервером и клиентом. На эту тему предлагаю долго не мудрить и сразу для себя ответить на главные вопросы:
  • Какой минимум данных надо посылать на сервер, чтобы отправить sms-сообщение абоненту?
  • Какой минимум данных надо посылать с сервера, чтобы удовлетворить потребности клиента?
Что-то мне подсказывает, что для этого необходимо посылать следующее:
  • номер мобильного телефона, а также
  • текст sms-сообщения.
В принципе, двух этих характеристик достаточно для отправки, но мне сразу представляется случай, как sms-ка с поздравлением о дне рождения приходит вам в 3 часа утра, или 4! В этот момент я буду всем очень благодарен за то, что про меня не забыли! Поэтому, мы также будем посылать на сервер и
  • дату отправки sms-сообщения.
Следующее, что я бы хотел отправлять на сервер, так это
  • Тип сообщения.
Данный параметр не является обязательным, но он может нам очень сильно пригодиться в случае, если быстро понадобится сказать боссу о том, скольких из наших клиентов мы «обрадовали» своим известием, а также нарисовать какую-нибудь красивую статистику на этот счет.

И все же, я что-то забыл! Если еще немного порефлексировать, то стоит отметить, что клиент за раз может отправить на сервер как одно sms-сообщение, так и некоторое их количество. Другими словами, в одном пакете данных может быть от одного до бесконечности сообщений.

В результате мы получаем, что для отправки sms-сообщения нам необходимы следующие данные:

  • номер мобильного телефона,
  • текст sms-сообщения,
  • время отправки sms-сообщения абоненту,
  • тип сообщения.

На первый вопрос мы ответили, теперь необходимо ответить на второй вопрос. И пожалуй, я позволю себе немного с халтурить. Поэтому, с сервера мы будем присылать только булевы данные, значение которых имеет следующий смысл:

  • TRUE – пакет успешно дошел до сервера, прошел аутентификацию и встал в очередь для отправки sms-провайдеру
  • FALSE – во всех остальных случаях

На этом мы закончили описание постановки задачи! И наконец-то приступим к самому интересному – будем разбираться что за диковинный зверь этот SOAP!

2 С чем есть SOAP?

Вообще, изначально я не планировал ничего писать о том, что такое SOAP и хотел ограничиться ссылками на сайт w3.org с нужными спецификациями, а также ссылками на Wikipedia. Но в самом конце решил написать коротенькую справочку об этом протоколе.

И начну я свое повествование с того, что данный протокол обмена данными относится к подмножеству протоколов основанных на так называемой парадигме RPC (Remote Procedure Call, удалённый вызов процедур) антиподом которой является REST (Representational State Transfer, передача репрезентативного состояния). Более подробно об этом можно прочесть в Wikipedia, ссылки на статьи находятся в самом конце топика. Из этих статей нам надо уяснить следующее: «Подход RPC позволяет использовать небольшое количество сетевых ресурсов с большим количеством методов и сложным протоколом. При подходе REST количество методов и сложность протокола строго ограничены, из-за чего количество отдельных ресурсов может быть большим». Т.е., применительно к нам это означает, что на сайте в случае RPC подхода будет всегда один вход (ссылка) на сервис и какую процедуру вызывать для обработки поступающих данных мы передаем вместе с данными, в то время как при REST подходе на нашем сайте есть много входов (ссылок), каждая из которых принимает и обрабатывает только определенные данные. Если кто-то из читающих знает, как еще проще объяснить различие в данных подходах, то обязательно пишите в комментариях!

Следующее, что нам надо узнать про SOAP – данный протокол в качестве транспорта использует тот самый XML, что с одной стороны очень хорошо, т.к. сразу же в наш арсенал попадает вся мощь стека технологий основанных на данном языке разметки, а именно XML-Schema – язык описания структуры XML-документа (спасибо Wikipedia!), который позволяет производит автоматическую валидацию поступающих на сервер данных от клиентов.

И так, теперь мы знаем, что SOAP – протокол используемый для реализации удаленного вызова процедур и в качестве транспорта он использует XML! Если почитать статью на Wikipedia, то оттуда можно узнать еще и о том, что он может использоваться поверх любого протокола прикладного уровня, а не только в паре с HTTP (к сожалению, в данном топике мы будем рассматривать только SOAP поверх HTTP). И знаете, что мне во всем этом больше всего нравится? Если нет никаких догадок, то я дам подсказку – SOAP!… Всеравно не появилось догадок?… Вы точно прочли статью на Wikipedia?… В общем, не буду вас дальше мучить. Поэтому, сразу перейду к ответу: «SOAP (от англ. Simple Object Access Protocol - простой протокол доступа к объектам; вплоть до спецификации 1.2 )». Самое примечательное в этой строчке выделено курсивом! Я не знаю какие выводы сделали вы из всего этого, но мне видится следующее – поскольку данный протокол ну никак нельзя назвать «простым» (и видимо с этим согласны даже в w3), то с версии 1.2 он вообще перестал как-то расшифровываться! И стал называться SOAP, просто SOAP и точка.

Ну да ладно, прошу меня извинить, занесло немного в сторону. Как я писал ранее, в качестве транспорта используется XML, а пакеты, которые курсируют между клиентом и сервером называются SOAP-конвертами. Если рассматривать обобщенную структуру конверта, то он вам покажется очень знакомым, т.к. напоминает структуру HTML-страницы. В нем есть основной раздел – Envelop , который включает разделы Header и Body , либо Fault . В Body передаются данные и он является обязательным разделом конверта, в то время как Header является опциональным. В Header может передаваться авторизация, либо какие-либо иные данные, которые на прямую не относятся к входным данным процедур веб-сервиса. Про Fault особо рассказывать нечего, кроме того, что он приходит в клиент с сервера в случае возникновения каких-либо ошибок.

На этом мой обзорный рассказ про протокол SOAP заканчивается (более детально сами конверты и их структуру мы рассмотрим когда наши клиент и сервер наконец-то научатся запускать их друг в друга) и начинается новый – про компаньона SOAP под названием WSDL (Web Services Description Language). Да-да, это та самая штука, которая отпугивает большинство из нас от самой попытки взять и реализовать свое API на данном протоколе. В результате чего, мы обычно изобретаем свой велосипед с JSON в качестве транспорта. И так, что такое WSDL? WSDL – язык описания веб-сервисов и доступа к ним, основанный на языке XML (с) Wikipedia. Если из этого определения вам не становится понятным весь сакральный смысл данной технологии, то я попытаюсь описать его своими словами!

WSDL предназначен для того, чтобы наши клиенты могли нормально общаться с сервером. Для этого в файле с расширением «*.wsdl» описывается следующая информация:

  • Какие пространства имен использовались,
  • Какие схемы данных использовались,
  • Какие типы сообщений веб-сервис ждет от клиентов,
  • Какие данные принадлежат каким процедурам веб-сервиса,
  • Какие процедуры содержит веб-сервис,
  • Каким образом клиент должен вызывать процедуры веб-сервиса,
  • На какой адрес должны отправляться вызовы клиента.
Как видно, данный файл и есть весь веб-сервис. Указав в клиенте адрес WSDL-файла мы будем знать об любом веб-сервисе все! В результате, нам не надо абсолютно ничего знать о том, где расположен сам веб-сервис. Достаточно знать адрес расположения его WSDL-файла! Скоро мы узнаем, что не так страшен SOAP как его малюют (с) русская пословицы.

3 Введение в XML-Schema

Теперь мы много чего знаем о то, что такое SOAP, что находится у него внутри и имеем обзорное представление о том, какой стек технологий его окружает. Поскольку, прежде всего SOAP представляет собой способ взаимодействия между клиентом и сервером, и в качестве транспорта для него используется язык разметки XML, то в данном разделе мы немного разберемся каким образом происходит автоматическая валидация данных посредством XML-схем.

Основная задачи схемы – описать структуру данных которые мы собираемся обрабатывать. Все данные в XML-схемах делятся на простые (скалярные) и коплексные (структуры) типы. К простым типам относятся такие типы как:

  • строка,
  • число,
  • булево значение,
  • дата.
Что-то очень простое, у чего внутри нет расширений. Их антиподом являются сложные комплексные типы. Самый простой пример комплексного типа, который приходит всем в голову – объекты. Например, книга. Книга состоит из свойств: автор , название , цена , ISBN номер и т.д. И эти свойства, в свою очередь, могут быть как простыми типами, так и комплексными. И задача XML-схемы это описать.

Предлагаю далеко не ходить и написать XML-схему для нашего sms-сообщения! Ниже представлено xml-описание sms-сообщения:

71239876543 Тестовое сообщение 2013-07-20T12:00:00 12
Схема нашего комплексного типа будет выглядеть следующим образом:


Эта запись читается следующим образом: у нас есть переменная «message » типа «Message » и есть комплексный тип с именем «Message », который состоит из последовательного набора элементов «phone » типа string , «text » типа string , «date » типа dateTime , «type » типа decimal . Эти типы простые и уже определены в описании схемы. Поздравляю! Мы только что написали нашу первую XML-схему!

Думаю, что значение элементов «element » и «complexType » вам стало все более-менее понятно, поэтому не будем на них больше заострять внимание и переключимся сразу же на элемент-композитор «sequence ». Когда мы используем элемент-композитор «sequence » мы сообщаем о том, что элементы включенные в него должны всегда располагаться в указанной в схеме последовательности, а также все из них являются обязательными. Но не стоит отчаиваться! В XML-схемах есть еще два элемента-композитора: «choice » и «all ». Композитор «choice » сообщает о том, что должен быть какой-то один из перечисленных в нем элементов, а композитор «all » – любая комбинация перечисленных элементов.

Как вы помните, то в первом разделе топика мы договорились о том, что в пакете может передаваться от одного до бесконечности sms-сообщений. Поэтому предлагаю разобраться как такие данные декларируются в XML-схеме. Общая структура пакета может выглядеть следующим образом:

71239876543 Тестовое сообщение 1 2013-07-20T12:00:00 12 71239876543 Тестовое сообщение N 2013-07-20T12:00:00 12
Схема для такого комплексного типа будет выглядеть так:


В первом блоке идет знакомое нам декларирование комплексного типа «Message ». Если вы заметили, то в каждом простом типе, входящем в «Message », были добавлены новые уточняющие атрибуты «minOccurs » и «maxOccurs ». Как не трудно догадаться из названия, первый (minOccurs ) сообщает о том, что в данной последовательности должно быть минимум по одному элементу типа «phone », «text », «date » и «type », в то время как следующий (maxOccurs ) атрибут нам декларирует, что таких элементов в нашей последовательности максимум по-одному. В результате, когда мы пишем свои схемы для каких-либо данных, нам предоставляется широчайший выбор по их настройке!

Второй блок схемы декларирует элемент «messageList » типа «MessageList ». Видно, что «MessageList » представляет собой комплексный тип, который включает минимум один элемент «message », но максимальное число таких элементов не ограничено!

4 Пишем свой WSDL

Вы помните о том, что WSDL и есть наш веб-сервис? Надеюсь, что помните! Как мы его напишем, так на нем наш маленький веб-сервис и поплывет. Поэтому, предлагаю не халтурить.

Вообще, для того, чтобы у нас все работало правильно нам надо передавать клиенту WSDL-файл с правильным MIME-типом. Для этого необходимо настроить ваш веб-сервер соответствующим образом, а именно – установить для файлов с расширением «*.wsdl» MIME-тип равный следующей строке:

Application/wsdl+xml
Но на практике, я обычно отправлял посредством PHP HTTP-заголовок«text/xml »:

Header("Content-Type: text/xml; charset=utf-8");
и все прекрасно работало!

Хочу сразу предупредить, наш простенький веб-сервис будет иметь довольно внушительное описание, поэтому не пугайтесь, т.к. большая часть текста является обязательной водой и написав ее один раз можно постоянно копировать от одного веб-сервиса к другому!

Поскольку WSDL – это XML, то в самой первой строке необходимо прямо об этом и написать. Корневой элемент файла всегда должен называться «definitions »:


Обычно, WSDL состоит из 4-5 основных блоков. Самый первый блок – определение веб-сервиса или другими словами – точки входа.


Здесь написано, что у нас есть сервис, который называется – «SmsService ». В принципе, все имена в WSDL-файле могут быть вами изменены на какие только пожелаете, т.к. они не играют абсолютно никакой роли.

После этого мы объявляем о том, что в нашем веб-сервисе «SmsService » есть точка входа («port»), которая называется «SmsServicePort ». Именно в эту точку входа и будут отправляться все запросы от клиентов к серверу. И указываем в элементе «address » ссылку на файл-обработчик, который будет принимать запросы.

После того, как мы определили веб-сервис и указали для него точку входа – необходимо привязать к нему поддерживаемые процедуры:


Для этого перечисляется какие операции и в каком виде у будут вызываться. Т.е. для порта «SmsServicePort » определена привязка под именем «SmsServiceBinding », которая имеет тип вызова «rpc » и в качестве протокола передачи (транспорта) используется HTTP. Т.о., мы здесь указали, что будем осуществлять RPC вызов поверх HTTP. После этого мы описываем какие процедуры (operation ) поддерживаются в веб-сервисе. Мы будем поддерживать всего одну процедуру – «sendSms ». Через эту процедуру будут отправляться на сервер наши замечательные сообщения! После того, как была объявлена процедура, необходимо указать в каком виде будут передаваться данные. В данном случае указано, что будут использоваться стандартные SOAP-конверты.

После этого нам необходимо привязать процедуру к сообщениям:


Для этого мы указываем, что наша привязка («binding») имеет тип «SmsServicePortType » и в элементе «portType » с одноименным типу именем указываем привязку процедур к сообщениям. И так, входящее сообщение (от клиента к серверу) будет называться «sendSmsRequest », а исходящее (от сервера к клиенту) «sendSmsResponse ». Как и все имена в WSDL, имена входящих и исходящих сообщения – произвольные.

Теперь нам необходимо описать сами сообщения, т.е. входящие и исходящие:


Для этого мы добавляем элементы «message » с именами «sendSmsRequest » и «sendSmsResponse » соответственно. В них мы указываем, что на вход должен прийти конверт, структура которого соответствует типу данных «Request ». После чего с сервера возвращается конверт содержащий тип данных – «Response ».

Теперь надо сделать самую малость – добавить описание данных типов в наш WSDL-файл! И как вы думаете, как описываются в WSDL входящие и исходящие данные? Думаю, что вы уже все давно поняли и сказали сами себе, что при помощи XML-схем! И вы будете абсолютно правы!


Можно нас поздравить! Наш первый WSDL был написан! И мы еще на один шаг приблизились к достижению поставленной цели.
Далее мы разберемся с тем, что нам предоставляет PHP для разработки собственных распределенных приложений.

5 Наш первый SOAP-сервер

Ранее я писал, что для создания SOAP-сервера на PHP мы будем использовать встроенный класс SoapServer. Для того, чтобы все дальнейшие действия происходили также как и у меня, вам понадобиться немного подкрутить свой PHP. Если быть еще точнее, то необходимо убедиться, что у вас установлено расширение «php-soap». Как его поставить на ваш веб-сервере лучше всего прочитать на официальном сайте PHP (см. список литературы).

После того, как все было установлено и настроено нам необходимо будет создать в корневой папке вашего хостинга файл «smsservice.php » со следующим содержанием:

setClass("SoapSmsGateWay"); //Запускаем сервер $server->handle();
То, что находится выше строчки с функцией «ini_set», надеюсь, что объяснять не надо. Т.к. там определяется какие HTTP-заголовки мы будем отправлять с сервера клиенту и настраивается окружение. В строчке с «ini_set» мы отключаем кеширование WSDL-файла для того, чтобы наши изменения в нем сразу же вступали в действие на клиенте.

Теперь мы подошли к серверу! Как видим, весь SOAP-сервер занимает всего лишь три строки! В первой строке мы создаем новый экземпляр объекта SoapServer и передаем ему в конструктор адрес нашего WSDL-описания веб-сервиса. Теперь мы знаем, что он будет располагаться в корне хостинга в файле с говорящим именем «smsservice.wsdl.php ». Во второй строке мы сообщаем SOAP-серверу какой класс необходимо дергать для того, чтобы обработать поступивший с клиента конверт и вернуть конверт с ответом. Как вы могли догадаться, именно в этом классе будет описан наш единственный метод sendSms . В третьей строке мы запускаем сервер! Все, наш сервер готов! С чем я нас всех и поздравляю!

Теперь нам необходимо создать WSDL-файл. Для этого можно либо просто скопировать его содержимое из предыдущего раздела, либо позволить себе вольности и немного его «шаблонизировать»:

"; ?> /" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" name="SmsWsdl" xmlns="http://schemas.xmlsoap.org/wsdl/"> /"> /smsservice.php" />
На этом этапе получившийся сервер нас должен устроить полностью, т.к. поступающие к нему конверты мы можем логировать и потом спокойно анализировать приходящие данные. Для того, чтобы мы могли что-либо получать на сервер, нам необходим клиент. Поэтому давайте им и займемся!

6 SOAP-клиент на подходе

Прежде всего нам надо создать файл, в котором будем писать клиент. Как обычно, мы его создадим в корне хоста и назовем «client.php », а внутри напишем следующее:

messageList = new MessageList(); $req->messageList->message = new Message(); $req->messageList->message->phone = "79871234567"; $req->messageList->message->text = "Тестовое сообщение 1"; $req->messageList->message->date = "2013-07-21T15:00:00.26"; $req->messageList->message->type = 15; $client = new SoapClient("http://{$_SERVER["HTTP_HOST"]}/smsservice.wsdl.php", array("soap_version" => SOAP_1_2)); var_dump($client->sendSms($req));
Опишем наши объекты. Когда мы писали WSDL в нем для входящего на сервер конверта описывались три сущности: Request , MessageList и Message . Соответственно классы Request , MessageList и Message являются отражениями этих сущностей в нашем PHP-скрипте.

После того, как мы определили объекты, нам необходимо создать объект ($req ), который будем отправлять на сервер. После чего идут две самые заветные для нас строки! Наш SOAP-клиент! Верите или нет, но этого достаточно для того, чтобы на наш сервер начали сыпаться сообщения от клиента, а также для того, чтобы наш сервер успешно их принимал и обрабатывал! В первой из них мы создаем экземпляр класса SoapClient и передаем в его конструктор адрес расположения WSDL-файла, а в параметрах явно указываем, что работать мы будем по протоколу SOAP версии 1.2. В следующей строке мы вызываем метод sendSms объекта $client и сразу же выводим в браузере результат.
Давайте запусти и посмотрим что-же у нас наконец-то получилось!

Мне с сервера вернулся следующий объект:

Object(stdClass) public "status" => boolean true
И это замечательно, т.к. теперь мы точно знаем о том, что наш сервер работает и не просто работает, но еще и может возвращать на клиент какие-то значения!

Теперь посмотрим на лог, который мы предусмотрительно ведем на серверной стороне! В первой его части мы видим необработанные данные, которые поступили на сервер:

79871234567 Тестовое сообщение 1 2013-07-21T15:00:00.26 15
Это и есть конверт. Теперь вы знаете как он выглядит! Но постоянно на него любоваться нам вряд ли будет интересно, поэтому давайте десереализуем объект из лог-файла и посмотрим все ли у нас хорошо:

Object(stdClass) public "messageList" => object(stdClass) public "message" => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 1" (length=37) public "date" => string "2013-07-21T15:00:00.26" (length=22) public "type" => string "15" (length=2)
Как видим, объект десериализовался правильно, с чем я нас всех хочу поздравить! Далее нас ждет что-то более интересно! А именно – мы будем отправлять клиентом на сервер не одно sms-сообщение, а целую пачку (если быть точнее, то целых три)!

7 Отправляем сложные объекты

Давайте подумаем над тем, как же нам передать целую пачку сообщений на сервер в одном пакете? Наверно, самым простым способом будет организация массива внутри элемента messageList! Давайте это сделаем:

// создаем объект для отправки на сервер $req = new Request(); $req->messageList = new MessageList(); $msg1 = new Message(); $msg1->phone = "79871234567"; $msg1->text = "Тестовое сообщение 1"; $msg1->date = "2013-07-21T15:00:00.26"; $msg1->type = 15; $msg2 = new Message(); $msg2->phone = "79871234567"; $msg2->text = "Тестовое сообщение 2"; $msg2->date = "2014-08-22T16:01:10"; $msg2->type = 16; $msg3 = new Message(); $msg3->phone = "79871234567"; $msg3->text = "Тестовое сообщение 3"; $msg3->date = "2014-08-22T16:01:10"; $msg3->type = 17; $req->messageList->message = $msg1; $req->messageList->message = $msg2; $req->messageList->message = $msg3;
В наших логах числится, что пришел следующий пакет от клиента:

79871234567 Тестовое сообщение 1 2013-07-21T15:00:00.26 15 79871234567 Тестовое сообщение 2 2014-08-22T16:01:10 16 79871234567 Тестовое сообщение 3 2014-08-22T16:01:10 17
Что за ерунда, скажете вы? И будете правы в некотором смысле, т.к. только что мы узнали о том, что какой объект ушел от клиента, то абсолютно в том же виде он пришел к нам на сервер в виде конверта. Правда, sms-сообщения сериализовались в XML не так, как нам было необходимо – они должны были быть обернуты в элементы message , а не в Struct . Теперь посмотрим в каком виде приходит такой объект в метод sendSms :

Object(stdClass) public "messageList" => object(stdClass) public "message" => object(stdClass) public "Struct" => array (size=3) 0 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 1" (length=37) public "date" => string "2013-07-21T15:00:00.26" (length=22) public "type" => string "15" (length=2) 1 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 2" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "16" (length=2) 2 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 3" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "17" (length=2)
Что нам дает это знание? Только то, что выбранный нами путь не является верным и мы не получили ответа на вопрос – «Как нам на сервере получить правильную структуру данных?». Но я предлагаю не отчаиваться и попробовать привести наш массив к типу объект :

$req->messageList->message = (object)$req->messageList->message;
В этом случае, нам придет уже другой конверт:

79871234567 Тестовое сообщение 1 2013-07-21T15:00:00.26 15 79871234567 Тестовое сообщение 2 2014-08-22T16:01:10 16 79871234567 Тестовое сообщение 3 2014-08-22T16:01:10 17
Пришедший в метод sendSms объект имеет следующую структуру:

Object(stdClass) public "messageList" => object(stdClass) public "message" => object(stdClass) public "BOGUS" => array (size=3) 0 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 1" (length=37) public "date" => string "2013-07-21T15:00:00.26" (length=22) public "type" => string "15" (length=2) 1 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 2" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "16" (length=2) 2 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 3" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "17" (length=2)
Как по мне, то «от перемены мест слагаемых – сумма не меняется» (с). Что BOGUS , что Struct – цель нами до сих пор не достигнута! А для ее достижения нам необходимо сделать так, чтобы вместо этих непонятных названий отображалось наше родное message . Но как этого добиться, автору пока не известно. Поэтому единственное, что мы можем сделать – избавить от лишнего контейнера. Другими словами, мы сейчас сделаем так, чтобы вместо message стал BOGUS ! Для этого изменим объект следующим образом:

// создаем объект для отправки на сервер $req = new Request(); $msg1 = new Message(); $msg1->phone = "79871234567"; $msg1->text = "Тестовое сообщение 1"; $msg1->date = "2013-07-21T15:00:00.26"; $msg1->type = 15; $msg2 = new Message(); $msg2->phone = "79871234567"; $msg2->text = "Тестовое сообщение 2"; $msg2->date = "2014-08-22T16:01:10"; $msg2->type = 16; $msg3 = new Message(); $msg3->phone = "79871234567"; $msg3->text = "Тестовое сообщение 3"; $msg3->date = "2014-08-22T16:01:10"; $msg3->type = 17; $req->messageList = $msg1; $req->messageList = $msg2; $req->messageList = $msg3; $req->messageList = (object)$req->messageList;
Вдруг нам повезет и из схемы подтянется правильное название? Для этого посмотрим на пришедший конверт:

79871234567 Тестовое сообщение 1 2013-07-21T15:00:00.26 15 79871234567 Тестовое сообщение 2 2014-08-22T16:01:10 16 79871234567 Тестовое сообщение 3 2014-08-22T16:01:10 17
Да, чуда не произошло! BOGUS – не победим! Пришедший в sendSms объект в этом случае будет выглядеть следующим образом:

Object(stdClass) public "messageList" => object(stdClass) public "BOGUS" => array (size=3) 0 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 1" (length=37) public "date" => string "2013-07-21T15:00:00.26" (length=22) public "type" => string "15" (length=2) 1 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 2" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "16" (length=2) 2 => object(stdClass) public "phone" => string "79871234567" (length=11) public "text" => string "Тестовое сообщение 3" (length=37) public "date" => string "2014-08-22T16:01:10" (length=19) public "type" => string "17" (length=2)
Как говорится – «Почти»! На этой (немного печальной) ноте предлагаю потихонечку закругляться и сделать некоторые для себя выводы.

8 Заключение

Наконец-то мы добрались сюда! Давайте определимся с тем, что вы теперь умеете делать:
  • вам по силам написать необходимый для вашего веб-сервиса WSDL-файл;
  • вы без всяких проблем можете написать свой собственный клиент способный общаться с сервером по протоколу SOAP;
  • вы можете написать свой собственный сервер общающийся с окружающим миром по SOAP;
  • вы можете отправлять массивы однотипных объектов на сервер со своего клиента (с некоторыми ограничениями).
Также, мы сделали для себя некоторые открытия в ходе нашего небольшого исследования:
  • нативный класс SoapClient не умеет правильно сериализовывать однотипные структуры данных в XML;
  • при сериализации массива в XML он создает лишний элемент с именем Struct ;
  • при сериализации объекта в XML он создает лишний элемент с именем BOGUS ;
  • BOGUS меньшее зло чем Struct из-за того, что конверт получается компактнее (не добавляются лишние namespace"ы в XML заголовке конверта);
  • к сожалению, класс SoapServer автоматически не валидирует данные конверта нашей XML-схемой (возможно, и другие сервера этого не делают).