Как сделать SOAP-вызов PHP с использованием класса SoapClient. Практический пример soap web-службы на 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-схемой (возможно, и другие сервера этого не делают).

Всем привет!
Так случилось, что в последнее время я стал заниматься разработкой веб-сервисов. Но сегодня топик не обо мне, а о том, как нам написать свой 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-схемой (возможно, и другие сервера этого не делают).

Не буду останавливаться на вопросе, что такое веб-сервисы и зачем они нужны. В сети очень много статей на эту тему. Просто постараюсь вкратце показать, каким простым способом возможно создание клиента к любому веб-сервису на php.

Настройка

Для использования SOAP в php необходимо подключить модуль SOAP (входит в дистрибутив php5). Под windows это делается просто – необходимо дописать (именно дописать, так как эта строка там не просто закомментирована, она отсутствует вообще) в php.ini :
extension=php_soap.dll

Не забудьте перезапустить сервер, если php у вас установлен как модуль.


Создание SOAP-клиента по WSDL-документу

Создание SOAP-клиента обычно происходит по WSDL-документу , который представляет собой XML-документ в определенном формате, полностью описывающий тот или иной веб-сервис. За подробностями по поводу WSDL – отправляю Вас на сайт консорциума W3C - http://www.w3.org/TR/2005/WD-wsdl20-soap11-binding-20050510/ .

Главное же, что необходимо знать для того, чтобы построить клиента к веб-сервису – это знать URL его WSDL-документа.
Для примера возьмем веб-сервис "Currency Exchange Rate" от xmethods.com. Адрес этого веб-сервиса, который позволяет получать курсы валют в режиме онлайн - http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl .

Второй важный момент – из описания веб-сервиса необходимо получить информацию о том, какие методы этот сервис предоставляет, и какие параметры мы должны передавать ему в качестве входных значений (очень похоже на вызов обычной функции php или метода класса). Обычно эта информация содержится в описании сервиса на его сайте. Наш веб-сервис для получения курса валют предоставляет метод getRate(), которому в качестве аргументов передаются коды валют.

И последнее – важно знать, что ожидать в качестве ответа: сколько значений, какого типа и т.п. Это также можно получить из описания.
А в результате код получается очень простым и компактным, почти элементарным:

// Использование Web-сервиса
// "Currency Exchange Rate" от xmethods.com

// Создание SOAP-клиента по WSDL-документу
$client = new SoapClient("http://www.xmethods.net/sd/2001/CurrencyExchangeService.wsdl");

// Поcылка SOAP-запроса и получение результата
$result = $client->getRate("us", "russia");

Echo ‘Текущий курс доллара: ’, $result, ‘ рублей’;
?>

Как видно из кода в конструктор класса SoapClient необходимо передать URL WSDL-документа и получить объект для работы с нужным веб-сервисом. Затем вызывается метод этого объекта, имя которого совпадает с именем самого метода веб-сервиса. Возвращает же этот метод желаемый нами результат.

Итак, этот простой пример иллюстрирует нам принцип построения SOAP-клиента для веб-сервисов на php. Однако в реальном приложении еще о многом придется позаботиться, в частности о том, что в момент обращения к веб-сервису он может быть временно недоступен или возвращать ошибку. Явно напрашивается использование блока try/catch/throw :)

(9)

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

  • Контактный идентификатор
  • Контактное лицо
  • Общее описание
  • Количество

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

$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 с данными:

  • ID контакта: 100
  • Контактное лицо: John
  • Общее описание: Баррель нефти
  • Количество: 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,);

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

Answers

Код.

Это то, что вам нужно сделать на стороне 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); вы увидите этот результат, поскольку ваш веб-сервис ожидает:

Array ( => Contact Object ( => 100 => John) => Barrel of Oil => 500)

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

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

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

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

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

например:

Require_once "WSDLInterpreter-v1.0.0/WSDLInterpreter.php"; $wsdlLocation = "?wsdl"; $wsdlInterpreter = new WSDLInterpreter($wsdlLocation); $wsdlInterpreter->savePHP(".");

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

Например: - Мой 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
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" => " ", "language" => "es")); $result = $client->createOrder($params); var_dump($result);

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

Вы также можете использовать 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 в качестве имени операции, например:

Вам нужен многомерный массив, вы можете попробовать следующее:

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

в PHP массив является структурой и очень гибким. Обычно с вызовами мыла я использую оболочку XML так неуверенно, если она будет работать.

РЕДАКТИРОВАТЬ:

То, что вы, возможно, захотите попробовать, это создать json-запрос для отправки или использования этого для создания xml-покупки, следующего за тем, что находится на этой странице: http://onwebdev.blogspot.com/2011/08/php-converting-rss-to-json.html

Вам нужно объявить договор класса

Class Contract { public $id; public $name; } $contract = new Contract(); $contract->id = 100; $contract->name = "John"; $params = array("Contact" => $contract, "description" => "Barrel of Oil", "amount" => 500,);

$params = array($contract, "description" => "Barrel of Oil", "amount" => 500,);

$response = $client->__soapCall("Function1", array("FirstFunction" => $params));

$response = $client->__soapCall("Function1", $params);

Большой вопрос, потому что многие разработчики, даже опытные, смущены тем, как PHP обрабатывает массивы в циклах foreach. В стандартном цикле foreach PHP создает копию массива, который используется в цикле. Копия отбрасывается сразу же после завершения цикла. Это прозрачно в работе простого цикла foreach. Например:

$set = array("apple", "banana", "coconut"); foreach ($set AS $item) { echo "{$item}\n"; }

Эти результаты:

Apple banana coconut

Таким образом, копия создается, но разработчик не замечает, потому что исходный массив не ссылается в цикле или после завершения цикла. Однако, когда вы пытаетесь изменить элементы в цикле, вы обнаружите, что они не изменены при завершении:

$set = array("apple", "banana", "coconut"); foreach ($set AS $item) { $item = strrev ($item); } print_r($set);

Эти результаты:

Array ( => apple => banana => coconut)

Любые изменения от оригинала не могут быть отмечены, на самом деле никаких изменений с оригиналом не происходит, даже если вы явно присвоили значение $ item. Это связано с тем, что вы работаете над $ item, поскольку он отображается в копии $ set, над которым работает. Вы можете переопределить это, захватив $ item по ссылке, например:

$set = array("apple", "banana", "coconut"); foreach ($set AS &$item) { $item = strrev($item); } print_r($set);

Эти результаты:

Array ( => elppa => ananab => tunococ)

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

$set = array("apple", "banana", "coconut"); foreach ($set AS $item) { $set = ucfirst($item); } print_r($set);

Эти результаты:

Array ( => apple => banana => coconut => Apple => Banana => Coconut)

Как показано в примере, PHP скопировал $ set и использовал его для перебора, но когда в цикле был использован $ set, PHP добавил переменные в исходный массив, а не в скопированный массив. В принципе, PHP использует только скопированный массив для выполнения цикла и назначения $ item. Из-за этого цикл выше выполняется только 3 раза и каждый раз добавляет другое значение в конец исходного набора $, оставляя исходный $ set с 6 элементами, но никогда не вступая в бесконечный цикл.

Однако, что, если бы мы использовали $ item по ссылке, как я уже упоминал ранее? Один символ добавлен к вышеуказанному тесту:

$set = array("apple", "banana", "coconut"); foreach ($set AS &$item) { $set = ucfirst($item); } print_r($set);

Результаты в бесконечном цикле. Обратите внимание, что это фактически бесконечный цикл, вам придется либо убить сценарий самостоятельно, либо дождаться завершения работы вашей ОС. Я добавил следующую строку в мой скрипт, поэтому у PHP будет очень быстро закончиться память, я предлагаю вам сделать то же самое, если вы собираетесь запускать эти бесконечные тесты цикла:

Ini_set("memory_limit","1M");

Итак, в этом предыдущем примере с бесконечным циклом мы видим причину, по которой PHP был написан, чтобы создать копию массива для перебора. Когда копия создается и используется только по структуре самой конструкции цикла, массив остается статичным во время выполнения цикла, поэтому вы никогда не столкнетесь с проблемами.