Pull to refresh

Zend_Soap_AutoDiscover и eAccelerator

Zend Framework

Всем привет! Хочу рассказать об одной проблеме, с которой я столкнулся совсем недавно. Надеюсь, что мой опыт, описанный в данной статье, поможет сэкономить часы и нервные клетки тем людям, кто занимается разработкой SOAP сервисов с использованием Zend Framework, и в частности класса Zend_Soap_AutoDiscover.
Проблема заключается в том, что Zend_Soap_AutoDiscover отрабатывает некорректно вкупе с использованием известного оптимизатора кода eAccelerator. А именно, если быть точным некорректно работает метод ReflectionClass::getDocComment(). Но… обо всем по порядку.

При разработке SOAP сервисов в 99% случаев перед программистом встает задача автоматической генерации здоровенных WSDL документов, которые используются в качестве описания сервисов. Такие WSDL содержат описания типов данных и методов, которые предоставляются сервисами. Насколько мне известно, многие программисты для решения этой задачи используют средства, предоставляемые Zend Framework, а именно Zend_Soap_AutoDiscover (ходят слухи, что для решения подобной задачи можно использовать библиотеку PEAR SOAP, но в данном случае не могу сказать ничего конкретного, т.к. не использовал её).

Не буду подробно останавливаться на описании как же использовать Zend_Soap, все это отлично описано в официальной документации или в статьях на этом сайте — например вот тут.

В чем основное преимущество использования Zend_Soap_AutoDiscover? При помощи этого класса мы можем автоматически строить WSDL, на основе классов, которые укажем в качестве параметров. Соотвественно WSDL будет динамически перестраиваться в зависимости от изменения классов (на основе которых он строится). Но есть один важный момент — Zend_Soap_AutoDiscover использует комментарии, оформленные в стиле PHPDoc, для определения типов данных параметров методов. Поэтому код должен быть хорошо задокументирован.

Далее следует пример простого client-server взаимодействия.
Пусть у нас есть 3 скрипта:
soap-client.php — клиент SOAP сервиса
soap-server.php — реализация SOAP сервиса
soap-server-model.php — класс, на основе которого строится SOAP сервис

Листинг кода каждого скрипта прост и незатейлив и представлен ниже:

soap-client.php:
  1. require_once 'Zend/Soap/Client.php';
  2. $wsdlUri = 'http://localhost/soap-server.php?wsdl';
  3. try {
  4. $client = new Zend_Soap_Client($wsdlUri);
  5. echo $client->showSomething('test', 46);
  6. echo '<br />';
  7. echo 'the end';
  8. } catch (Exception $e) {
  9. echo 'Error: '. $e->getMessage();
  10. }
* This source code was highlighted with Source Code Highlighter.


soap-server.php:
  1. require_once 'soap-server-model.php';
  2. $wsdlUri = 'http://localhost/soap-server.php?wsdl';
  3. if(isset($_GET['wsdl'])) {
  4. require_once 'Zend/Soap/AutoDiscover.php';
  5. $autodiscover = new Zend_Soap_AutoDiscover();
  6. $autodiscover->setClass('SoapModel');
  7. $autodiscover->handle();
  8. } else {
  9. require_once 'Zend/Soap/Server.php';
  10. $soap = new Zend_Soap_Server($wsdlUri);
  11. $soap->setClass('SoapModel');
  12. $soap->handle();
  13. }
* This source code was highlighted with Source Code Highlighter.


soap-server-model.php:
  1. class SoapModel
  2. {
  3. /**
  4. * Method for testing SOAP server with Zend_Soap_AutoDiscover
  5. *
  6. * param string $word
  7. * param int $num
  8. * return string
  9. */
  10. public function showSomething($word, $num)
  11. {
  12. return 'Server said: '. $word. ' '. $num;
  13. }
  14. }
* This source code was highlighted with Source Code Highlighter.


В php.ini настройки SOAP (на стороне клиента) имеют следующий вид:
[soap]
soap.wsdl_cache_enabled=0
soap.wsdl_cache_dir="/tmp"
soap.wsdl_cache_ttl=18000
soap.wsdl_cache_limit = 0

то есть в целях тестирования отключаем кэширование WSDL

Казалось бы все круто, запускаем скрипт клиента и вместо ожидаемого «Server said: test 46» видим лишь «the end». Смотрим возвращаемый WSDL (в примере он доступен по URL: localhost/soap-server.php?wsdl&a=1), и что же мы видим — вместо ожидаемого:

  1. <?xml version=«1.0»?>
  2. <definitions xmlnsschemas.xmlsoap.org/wsdl» xmlns:tnslocalhost/soap-server.php» xmlns:soapschemas.xmlsoap.org/wsdl/soap» xmlns:xsdwww.w3.org/2001/XMLSchema» xmlns:soap-encschemas.xmlsoap.org/soap/encoding» xmlns:wsdlschemas.xmlsoap.org/wsdl» name=«SoapModel» targetNamespacelocalhost/soap-server.php»>
  3. <types>
  4. <xsd:schema targetNamespacelocalhost/soap-server.php»/>
  5. </types>
  6. <portType name=«SoapModelPort»>
  7. <operation name=«showSomething»>
  8. <documentation>Method for testing SOAP server with Zend_Soap_AutoDiscover</documentation>
  9. <input message=«tns:showSomethingIn»/>
  10. <output message=«tns:showSomethingOut»/>
  11. </operation>
  12. </portType>
  13. <binding name=«SoapModelBinding» type=«tns:SoapModelPort»>
  14. <soap:binding style=«rpc» transportschemas.xmlsoap.org/soap/http»/>
  15. <operation name=«showSomething»><soap:operation soapActionlocalhost/soap-server.php#showSomething»/>
  16. <input>
  17. <soap:body use=«encoded» encodingStyleschemas.xmlsoap.org/soap/encoding» namespacelocalhost/soap-server.php»/>
  18. </input>
  19. <output>
  20. <soap:body use=«encoded» encodingStyleschemas.xmlsoap.org/soap/encoding» namespacelocalhost/soap-server.php»/>
  21. </output>
  22. </operation>
  23. </binding>
  24. <service name=«SoapModelService»>
  25. <port name=«SoapModelPort» binding=«tns:SoapModelBinding»>
  26. <soap:address locationlocalhost/soap-server.php»/>
  27. </port>
  28. </service>
  29. <message name=«showSomethingIn»>
  30. <part name=«word» type=«xsd:string»/>
  31. <part name=«num» type=«xsd:int»/>
  32. </message>
  33. <message name=«showSomethingOut»>
  34. <part name=«return» type=«xsd:string»/>
  35. </message>
  36. </definitions>
* This source code was highlighted with Source Code Highlighter.


выдается следующий код:

  1. <?xml version=«1.0»?>
  2. <definitions xmlnsschemas.xmlsoap.org/wsdl» xmlns:tnslocalhost/soap-server.php» xmlns:soapschemas.xmlsoap.org/wsdl/soap» xmlns:xsdwww.w3.org/2001/XMLSchema» xmlns:soap-encschemas.xmlsoap.org/soap/encoding» xmlns:wsdlschemas.xmlsoap.org/wsdl» name=«SoapModel» targetNamespacelocalhost/soap-server.php»>
  3. <types>
  4. <xsd:schema targetNamespacelocalhost/soap-server.php»/>
  5. </types>
  6. <portType name=«SoapModelPort»>
  7. <operation name=«showSomething»>
  8. <documentation>showSomething</documentation>
  9. <input message=«tns:showSomethingIn»/>
  10. </operation>
  11. </portType>
  12. <binding name=«SoapModelBinding» type=«tns:SoapModelPort»>
  13. <soap:binding style=«rpc» transportschemas.xmlsoap.org/soap/http»/>
  14. <operation name=«showSomething»>
  15. <soap:operation soapActionlocalhost/soap-server.php#showSomething»/>
  16. <input>
  17. <soap:body use=«encoded» encodingStyleschemas.xmlsoap.org/soap/encoding» namespacelocalhost/soap-server.php»/>
  18. </input>
  19. </operation>
  20. </binding>
  21. <service name=«SoapModelService»>
  22. <port name=«SoapModelPort» binding=«tns:SoapModelBinding»>
  23. <soap:address locationlocalhost/soap-server.php»/>
  24. </port>
  25. </service>
  26. <message name=«showSomethingIn»>
  27. <part name=«word» type=«xsd:anyType»/>
  28. <part name=«num» type=«xsd:anyType»/>
  29. </message>
  30. </definitions>
* This source code was highlighted with Source Code Highlighter.


Вообщем, после долгих поисков неисправности, изучения мануалов и криков WTF o.O оказалось, что все прекрасно работает после отключения eAccelerator. Почему же так происходит? Zend_Soap_AutoDiscover в своей внутренней реализации использует механизм Reflections (если быть точным за это отвечает класс Zend_Server_Reflection), для получения параметров из комментариев к методам. eAccelerator же в свою очередь после первого обращения к скрипту во время создания байткода удаляет все комментарии, в результате чего мы имеем следующую вышеописанную трабблу. Логично, но достаточно нетривиально.

Какие же пути решения?
1. Отказаться от eAccelerator. И использовать например APC (лично проверял — с APC подобных проблем не наблюдалось).
2. Использовать фильтры eAccelerator-а, для того чтобы не оптимизировать файлы, в которых важны комментарии. Делается это примерно так:
ini_set('eaccelerator.filter', '!soap-server-model.php');

3. Решение от пользователя red_pilot: при установке сконфигурять eAccelerator с ключем with-eaccelerator-doc-comment-inclusion, то есть:
./configure -–with-eaccelerator-doc-comment-inclusion


P.S. Вообще выше обозначенная проблема несколько глубже, чем просто «какой-то там класс Zend-а не работает с eAccelerator». Пусть это не часто, но тем не менее механизмы Reflections (ReflectionClass::getDocComment()) используются при разработке, и зачастую комментарии к классам могут играть роль в разрабатываемом функционале.

P.S.S. Как это часто бывает, тулзы типо eAccelerator используются на production-серверах, и очень редко, когда кто-либо устанавливает их у себя на локали. В итоге, идеально работающий на локали код, может совершенно нетривиально не работать на production.

P.S.S.S. Zend_Soap, как и большинство других компонент Zend можно использовать отдельно от самого Framework-а. Так, например, в текущем разрабатываемом проекте мы используем лишь 3 компонента ZF, а именно Zend_Db, Zend_Form и Zend_Soap. И если, например, кто-то захочет заюзать у себя Zend_Soap_AutoDiscover, то вовсе не обязательно чтобы весь проект был на ZF. Достаточно перенести все необходимые классы ZF. По правде говоря, кроме использования супер-полезного Zend_Soap_AutoDiscover, в остальном Zend_Soap слабо расширяет встроенную Soap функциональность. Так что если вы не озадачены созданием Soap-сервисов, то вряд ли будет сильно полезно менять нативный SoapClient на Zend_Soap_Client.
Tags:phpreflectionseacceleratorzend frameworkzend_soapzend_soap_autodiscover
Hubs: Zend Framework
Total votes 19: ↑16 and ↓3+13
Views2.7K

Popular right now

Top of the last 24 hours