2016-01-14 28 views
12

我使用PHP的SOAPClient class與SOAP API進行通信。其中一個選項有讓您重新映射在WSDL文件用自己的類指定的類型:構造函數不從SOAP響應對象中調用

The classmap option can be used to map some WSDL types to PHP classes. This option must be an array with WSDL types as keys and names of PHP classes as values.

創建我的客戶是這樣的:

$api = new SOAPClient('http://example.com/soap.wsdl', [ 
    'location' => 'http://example.com/soap/endpoint', 
    'soap_version' => SOAP_1_2, 
    'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP, 
    'cache_wsdl' => WSDL_CACHE_BOTH, 
    'classmap' => [ 
     'APIResultObject' => 'Result' 
    ], 
    # TODO: Set for debug only? 
    'trace' => TRUE, 
    'exceptions' => TRUE 
]); 

這工作,當我打電話$api->method('param'),我得到一個Result對象(而不僅僅是一個StdClass對象)。問題是從來沒有調用Result::__construct()方法,所以一些私有屬性Result從不設置。

這裏是Result是:

class DataClass{ 
    protected $data; 

    function __construct(){ 
     $this->data = ['a' => 0, 'b' => 1, 'c' => 2]; 
    } 
} 

class Result extends DataClass{ 
    public $value, $name, $quantity; 

    function __construct(array $values){ 
     parent::__construct(); 

     foreach(['value', 'name', 'quantity'] as $var){ 
      $this->$var = isset($values[$var]) ? $values[$var] : NULL; 
     } 
    } 

    function getData(){ 
     return $this->data[$this->name]; 
    } 
} 

發生了什麼事是我做$api->method('param')->getData()並得到以下錯誤:

Notice: Undefined property: Result::$data

我如何可以調用構造函數,我需要得到一個SOAP時響應?我嘗試使用__wakeup(),但這似乎也沒有工作。

P.S.我用一個小的解決方法「解決」了它,但我不認爲它是理想的。下面是我所做的:

function getData(){ 
    if($this->data === NULL){ 
     parent::__construct(); 
    } 

    return $this->data[$this->name]; 
} 
+0

您好,我只是好奇如何你使用$ api-> method('param') - > getName()時調用getData函數嗎?不要downvote我pls,我只是想學習的東西...我認爲該類應該有被稱爲方法的方法名稱,但我可以看到有任何方法getName()多數民衆贊成爲什麼我問... Thx提前你的回答:-) – Redrif

+0

@Redrif:那只是一個小錯字,我很抱歉。我試圖從我的真實代碼中複製和粘貼(並更改事物的名稱)。這實際上應該是'$ api-> method('param') - > getData()'。這裏沒有魔力,只是在問題上的拼寫錯誤。 –

回答

4

這是已知的行爲(bug report)。

由於someoned在錯誤報告建議(miceleparkip在網絡點德):

This is not a bug. It's quite normal.

The soap object is created on the server side. So the constructor is just called on the server.

我分享她的位置。

隨後的評論(PHP在hotblocks點NL)在同一個bug報告有異議:

The server doesn't create objects, it sends XML. The client decodes that XML and creates the objects.

雖然這是從技術的觀點蓬無可爭議真實的,可以說是創造了「抽象」對象在服務器端。無論是第一次轉換爲XML還是在客戶端重新構建,都是應用程序層不需要意識到的低級問題。

如果你的應用需要比由服務器提供的更多的功能對象,我會創建一個本地類,它由SOAPClient創建的對象作爲構造函數的參數:

class MySoapResultClass { 
    // whatever 
} 

class LocalApplicationClass { 

    public function __construct(MySoapResultClass $soapResult) { 

     // your local initialization code 
     $this->data = ['a' => 0, 'b' => 1, 'c' => 2]; 

     // then either extract your data from $soapResult, 
     // or just store a reference to it 
    } 

    public function getData(){ 
     return $this->data[$this->name]; 
    } 
} 

$api = new SOAPClient(...); 
$soapResult = $api->method('param'); 
$myUsefulObject = new LocalApplicationClass($soapResult); 
+1

對我來說,這甚至看起來像超過這個特定情況下的最佳實踐方法。 – FloydThreepwood

5

更新:另一個解決辦法

你可以在另一個類,將正確調用構造函數包裹SoapClient的。爲了節省麻煩,班級會檢查是否需要。仍然不理想,但我認爲這樣更簡單。

class ActiveSOAPClient extends SoapClient { 
    // Intercept all calls to class. 
    public function __call($func, $params) { 
     // Pass it to parent class. 
     $ret = parent::__call($func, $params); 
     // If the answer is an object... 
     if (is_object($ret) 
      // Taboo functions that will pass unhindered. 
      // && (!in_array($func, [ ARRAY OF EXCLUDED_FUNCTIONS ])) 
     ) { 
      // ...and the object is in the auto classmap... 
      if (false !== array_search(get_class($ret), $this->_classmap, true)) { 
       // ...then assume it's an incomplete object and try activating it. 
       $ret->__construct(); 
      } 
     } 
     return $ret; 
    } 
} 

的優點是ActiveSOAPClient類不需要擁有你自己的邏輯的任何信息,而你的邏輯並不需要改變。

的問題

我認爲這種行爲是有意還是一個已知的bug(即必須有背後的一些原因或問題),因爲在手冊頁是already noted as of seven years ago

我檢出了PHP 5.5.6的源代碼。據我可以讀取php_encoding.c代碼,

/* Struct encode/decode */ 
static zval *to_zval_object_ex(encodeTypePtr type, xmlNodePtr data, zend_class_entry *pce TSRMLS_DC) 
{ 
     zval *ret; 
     xmlNodePtr trav; 
     sdlPtr sdl; 
     sdlTypePtr sdlType = type->sdl_type; 
     zend_class_entry *ce = ZEND_STANDARD_CLASS_DEF_PTR; 
     zval *redo_any = NULL; 

     if (pce) { 
       ce = pce; 
     } else if (SOAP_GLOBAL(class_map) && type->type_str) { 
       zval    **classname; 
       zend_class_entry *tmp; 

       if (zend_hash_find(SOAP_GLOBAL(class_map), type->type_str, strlen(type->type_str)+1, (void**)&classname) == SUCCESS && 
        Z_TYPE_PP(classname) == IS_STRING && 
        (tmp = zend_fetch_class(Z_STRVAL_PP(classname), Z_STRLEN_PP(classname), ZEND_FETCH_CLASS_AUTO TSRMLS_CC)) != NULL) { 
         ce = tmp; 
       } 
     } 

...如果一個類映射被定義,並且是已知的,zend_fetch_class()被調用。

我認爲某些類型的ctor()函數應該在從節點獲取的值之後被調用,例如,在PDO :: fetchObject中(參見文件「ext/pdo/pdo_stmt.c」)。

目前,這似乎沒有完成。可能是因爲XML樹中對象的評估順序,這使得構造函數提供適當的參數非常棘手,但在這一點上,我只是猜測。

然而,你在那裏是當時沒有「官方」的解決方案正確的(你不能得到比源代碼更官方)。

黑客源

我試圖添加一個構造亞軍在PHP源代碼,只是爲了它的地獄。不幸的是,我似乎需要幾個不在我需要它們的範圍內的變量,所以我不得不改變一些結構來傳遞構造函數等等,這些結構在SOAP代碼中無處不在。

除了可能最簡單的情況下,一個無參數構造函數且沒有析構函數的對象,對代碼的必要修改根本不在我看來。

+0

我真的很喜歡你的'ActiveSOAPClient'創意!這使得它比在每個需要它的方法中放入'parent :: __ construct();'都容易。 –