Подключаем SOAP веб-сервисы в Yii2
Многие сайты и некоторые серверные приложения позволяют обращаться к ним по сети посредством стандартизированного протокола SOAP. Они выкладывают открытый API, через который позволяют вызывать некоторые их методы с передачей параметров. При этом масштабы таких систем могут быть совершенно разными: получение прогноза погоды, сеть 1C крупной организации или система бронирования авиабилетов.
В веб-разработке часто приходится интегрировать сторонние сервисы в свой сайт. Например, предположим, что нам нужно получить время восхода солнца, время захода и долготу дня для определённого города с веб-сервиса GisMeteo.
Мы на адрес http://ws.gismeteo.ru/Weather/Weather.asmx посылаем POST-запрос на вызов метода GetSunInfo в виде XML:
И с сервера возвращается нужный нам ответ GetSunInfoResponse в XML-формате:
Аналогично представим, что некий магазин открыл SOAP-сервис базы 1С для своих филиалов по адресу http://api.site.ru/ws/webservice.1cws , закрыв его от посторонних глаз HTTP-Basic авторизацией. Теперь работники других филиалов могут подключить свои экземпляры программы к этому общему сервису и просматривать списки заказов или отгрузок из общей базы и создавать свои:
На стороне сервера нужно только запрограммировать нужные методы вроде GetOrders и опубликовать сервис.
Это даёт возможность сделать корпоративный веб-портал для работников организации, подключенный к сервису 1С. Или даже разработать мобильное приложение.
Действительно, по этому адресу мы можем послать POST-запрос GetOrders :
и получить список заказов, возвращаемый в виде вложенного XML-файла в поле return :
Мы просто отправляем запрос на адрес http://api.site.ru/ws/webservice.1cws для вызова метода с полным именем http://api.site.ru/#WebService:GetOrders и оформляем XML запрос с использованием соответствующего пространства имён http://api.site.ru/ .
Эти три параметра должны быть именно такими, какими их предоставляет сервер.
Смотря на такие «адские» XML-структуры вам наверняка покажется, что это ужасно сложно использовать. На самом деле, это только эмоциональное первое впечатление. Как бы это ни выглядело сложным, вам не придётся разбираться в формате и что-то парсить.
В PHP для работы с веб-сервисами имеется класс SoapClient. Если вы работаете в Windows и столкнулись с отсутствием этого класса, то подключите расширение php_soap.dll в файле php.ini .
Работать с этим компонентом можно в двух режимах. Различаются они только способом формирования запросов.
Работа в WSDL-режиме
Обычно на сервере имеется специально сгенерированный WSDL-файл c описанием всех доступных методов и адресов веб-сервиса. У GisMeteo он выглядит так.
Для уже знакомого нам метода GetSunInfo в нём имеется отдельная секция с определением полей запроса и ответа:
Ниже находится информация о необходимом для его вызова значении action и используемых режимах работы:
И в самом конце файла расположена секция для определения location для работы по протоколам SOAP 1.1 и SOAP 1.2 соответственно:
Аналогичные секции присутствуют в WSDL файле, генерируемом веб-сервисом 1С.
Формат запроса заказов может быть таким:
Полное имя метода с пространством имён таким:
А адреса доступа такими:
Теперь достаточно указать этот файл при создании объекта класса SoapClient :
или воспользоваться локальной копией файла:
Так как сервис может быть закрыт HTTP Basic авторизацией, мы можем указать данные для доступа:
В классе SoapClient реализован магический метод __call , который позволяет работать с удалёнными методами прямо по их имени:
Результат $result не надо парсить вручную, так как он уже будет представлять из себя объект с полями, повторяющими XML-структуру ответа. Например, долготу дня можно будет получить так:
Аргументы метода могут передаваться в виде массива или объекта. Попробуем, например, получить курсы валют на текущий день по протоколу SOAP с сайта Центробанка с помощью метода GetCursOnDate . В отличие от получения погоды, этот метод возвращает ответ с единственным полезным нам полем any , в котором содержится вложенный XML документ с валютами. Но ничего страшного, так как его легко будет превратить в объект с помощью SimpleXMLElement .
Создадим файл curs.php :
и запустим его в консоли:
Через некоторое время мы увидим результат:
Здесь мы WSDL-файл загружаем прямо с сервера и кешируем его в памяти на некоторое время. Но если в каком-либо сервисе этот файл занимает несколько мегабайт, то для экономии трафика его лучше скачать к себе и подключать из локальной папки.
Вот и всё. Создаём клиент и выполняем методы. Никаких мучений с составлением и парсингом SOAP-запросов и ответов.
Работа в ручном режиме
Если у вас есть необходимость подключаться к какому-то специфическому веб-сервису, у которого отсутствует WSDL-файл, либо если вы хотите указывать все параметры вручную, то вам будет нужен режим работы без WSDL.
В данном случае клиенту неоткуда будет брать информацию об адресе сервиса, пространствах имён и полных имён методов для SOAPAction . Поэтому адрес сервиса location и пространство имён uri надо будет указать вручную:
Параметром exceptions мы включаем механизм генерирования исключений, позволяющий обрабатывать их конструкцией try < . >catch (SoapFault $e) . Другими параметрами можно указать, например, используемый прокси-сервер.
Все методы класса SoapClient доступны извне. Послать команду на сервер теперь нужно непосредственным вызовом __soapCall , указав имя метода для построения XML-запроса и полное имя SOAPAction. Получим из 1С список заказов за последние 90 дней:
Метод __soapCall позаботится о построении XML-структуры запроса и вызовет метод __doRequest , который отправит запрос на сервер.
Но при желании мы можем опуститься ещё ниже и вызвать метод __doRequest . В этом случае XML-запрос нужно составить самому:
Смешанное использование
Иногда встречаются ситуации, когда нужно использовать нестандартные параметры запроса. Например, специфические заголовки. Рассмотрим пример получения информации о пользователе из eBay:
Можно заметить, что здесь есть WSDL файл, но для работы нам нужно передавать токен авторизованного пользователя в дополнительных заголовках. И в этом случае нам помог вызов метода __soapCall .
Интеграция с веб-сервисом в Yii2
Аналогично нашим предыдущим скриптам, мы можем производить операции в любом месте нашего приложения:
То есть для данного случая нам просто нужно поместить WSDL-файл в любое место и прописать авторизационные данные в конфигурационном файле.
Это простейший случай, но использование этого кода, например, в контроллере не очень уместно. Вместо этого клиент целесообразнее вынести в отдельный компонент. Этим мы сейчас и займёмся.
Вместо использования динамически создаваемых структур или ассоциативных массивов для передачи аргументов запроса:
лучше использовать одноимённые запросам классы:
Это удобнее тем, что в любом хорошем редакторе или IDE работает автоподстановка полей и подсветка опечаток. Кроме того, в своём классе мы можем релизовать любой дополнительный функционал.
Например, создав базовый класс для наших команд, наследуемый от класса yii\base\Model :
мы превращаем все структуры в модели и легко можем добавить в них правила валидации:
и проверять корректность заполненных данных перед отправкой запроса:
И ещё один нюанс. У нас сделано так, что имя метода и класса его параметров совпадают:
Соответственно, имя метода можно получить из имени класса:
и вызывать этот метод у клиента динамически любым способом из этих двух:
Теперь, пользуясь этими нюансами, мы можем создать компонент приложения с единственным методом send , который будет автоматически определять имя метода и вызывать его у клиента:
и подключить его в конфигурационном файле:
чтобы теперь обращаться к нему как к Yii::$app->webservice :
Этого уже вполне достаточно для нормальной работы.
Но если у вас очень сложный сервис, ответы которого нужно довольно рутинно парсить (или ответ приходит в нестандартной кодировке, которую надо всегда расшифровывать), то можно наделить логикой и ответы.
Создадим базовый класс для ответа:
Теперь отнаследуем от него класс, который будет обрабатывать ответ метода получения курса валют GetCursOnDate . Одноимённый класс, но в папке response :
Дополним наш компонент функционалом оборачивания пришедшего от клиента ответа в полученный из имени метода класс:
После этого вместо голого ответа от клиента в переменной $response мы уже получим наш объект и сможем использовать его методы.
Теперь весь код получения курса доллара через SOAP клиент занимает всего несколько строк:
и может полноценно использоваться в любом месте приложения. Всё связанное с SOAP мы полностью инкапсулируем внутри нашего компонента webservice . Теперь в тестовой конфигурации можно легко подменить класс WebService , чтобы заменить рабочий компонент на его заглушку.
После того, как оставите комментарий к данной статье, советую прочесть ещё пару интересных статей: