Zend_Soap_AutoDiscover и eAccelerator


    Всем привет! Хочу рассказать об одной проблеме, с которой я столкнулся совсем недавно. Надеюсь, что мой опыт, описанный в данной статье, поможет сэкономить часы и нервные клетки тем людям, кто занимается разработкой 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.  
    3. $wsdlUri = 'http://localhost/soap-server.php?wsdl';
    4.  
    5. try {
    6.   $client = new Zend_Soap_Client($wsdlUri);
    7.   echo $client->showSomething('test', 46);
    8.   echo '<br />';
    9.   echo 'the end';
    10. } catch (Exception $e) {
    11.   echo 'Error: '. $e->getMessage();
    12. }
    * This source code was highlighted with Source Code Highlighter.


    soap-server.php:
    1. require_once 'soap-server-model.php';
    2.  
    3. $wsdlUri = 'http://localhost/soap-server.php?wsdl';
    4.  
    5. if(isset($_GET['wsdl'])) {
    6.   require_once 'Zend/Soap/AutoDiscover.php';
    7.   $autodiscover = new Zend_Soap_AutoDiscover();
    8.   $autodiscover->setClass('SoapModel');
    9.   $autodiscover->handle();
    10. } else {
    11.   require_once 'Zend/Soap/Server.php';
    12.   $soap = new Zend_Soap_Server($wsdlUri);
    13.   $soap->setClass('SoapModel');
    14.   $soap->handle();
    15. }
    * 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.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 15
    • 0
      eAccelerator же больше используется как оптимизатор чем обфускатор?
      • 0
        Просто не знал какое слово правильнее подобрать, конечно же в данном случае имеется ввиду создание и кэшироание байткода скриптов. Поправил несколько предложений в статье, чтобы выглядело более понятнее.
      • 0
        Текст не читал, но уверен, что тут проблема с рефлексией, т.к. Других проблем с акселератором обычно нет.
        Читайте доку от акселератора и пользуйтесь гуглом! Отключите вырезание комментов в настройках акселератора и будет счастье. Если многобукв не об этом, тогда извините!
        • +2
          Специально проверил, что по запросу eaccelerator reflection первый же результат решает проблему.
          --with-eaccelerator-doc-comment-inclusion
          • 0
            Да, спасибо. Но как написано в описании, появилась эта фича только с версии 0.9.5.
            • +5
              Сейчас сейчас конец июня 2011 года.

              Release-0.9.6.1 — 2010/05/31
              Release-0.9.6 — 2010/02/04
              Release-0.9.6-rc2 — 2010/01/26
              Release-0.9.6-rc1 — 2009/07/15
              Release-0.9.5.3 — 2008/05/18
              Release-0.9.5.2 — 2007/09/03
              Release-0.9.5.1 — 2007/05/06
              Release-0.9.5 — 2006/10/11
              Release-0.9.5-rc1 — 2006/07/25
              Release-0.9.5-beta2 — 2006/04/11
              Release-0.9.5-beta1 — 2006/02/15
              Release-0.9.4 — 2006/02/15
              • 0
                Да, точняк. Номер релиза не показатель! Счас добавлю этот способ решения в статью. Но в любом случае, по умолчанию, eaccelerator запускается без этого ключа, что как бы не очень здорово.
                • 0
                  в смысле «компиляется», а не «запускается»
            • +1
              Проблема в том, что когда отрабатывает eAccelerator, он перед этим потирает комменты. В итоге оптимизируется скрипт уже без комментов, которые так нужны в рефлексии.
              А про настройки eAccelerator — не нашел такой опции, что отключить вырезание комментов. Нашел только, как вообще не оптимизировать перечисленные заранее скрипты.
            • +2
              Да, есть такая проблема с eAccelerator'ом. Модные ныне аннотации в докблоках иногда тоже отваливаются по той же причине. При этом проблему можно очень долго искать. Так что вам + за то что подняли этот вопрос. Может меньше народа наступит на эти грабли.
              • 0
                Хотя статью можно было поменьше сделать и описать только суть.
              • +1
                Наступал на эти грабли еще 2 года назад, в результате чего сделал выбор в пользу APC, разница в производительности минимальна.
                • 0
                  Тоже очень долго ковырялся в ZF, пока не нашел причину в eAccelerator и вот этот тикет. Неприятный баг. Особенно если учесть, что в последнее время многое строится на аннотациях.
                  • НЛО прилетело и опубликовало эту надпись здесь

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.