PHP中原生類型的方法

來源:互聯網
上載者:User

引言

第一次,翻譯別人的文章,用四級英語的水平來翻譯~~囧,可能有很多不太恰當的地方,儘管拍磚(有些地方實在想不到恰當的翻譯,我同時貼出了原文和自己很low的翻譯)。

翻譯這篇文章用了我3個晚上一個中午~,先弄明白技術上大體再說什麼,然後在翻譯的~

這樣做的目的一方面鍛煉下自己的英文,一方面學習點國外的比較新的技術想法。

這篇文章主要講了對PHP中的原生類型實現物件導向的操作,通過擴充的方式實現,用來解決PHP中函數命名不規範、參數順序不規範、可讀性低的問題。

擴充的實現是通過改變ZEND引擎中調用物件導向方法時對應的處理函數來實現的,註冊一個函數,判斷物件導向調用者的類型,如果是IS_STRING則繼續自訂處理,否則返回ZEND預設的處理函數,看看下面的解釋

HOOKPHP代碼

PHP是解釋型語言,代碼被翻譯為中間位元組碼由ZEND引擎解析執行。PHP把中間位元組碼稱之為OPCODE,每個OPCODE對應ZEND底層的一個處理函數,ZEND引擎最終執行這個處理函數。實現HOOK功能只需要改變HOOK OPCODE對應的處理函數即可。

PHP中原生類型的物件導向的方法

幾天前,Anthony Ferrara寫下了一些對PHP未來的看法。我同意他的大部分觀點,但不是所有的。這篇文章關注的是一個特別的方面:將像字串、數組這樣的原生類型偽裝為“偽對象”,可以在這些原生類型上執行方法調用。

讓我們從幾個例子開始看看為什麼需要“偽對象”:

$str = "test foo bar";$str->length();      // == strlen($str)        == 12$str->indexOf("foo") // == strpos($str, "foo") == 5$str->split(" ")     // == explode(" ", $str)  == ["test", "foo", "bar"]$str->slice(4, 3)    // == substr($str, 4, 3)  == "foo"$array = ["test", "foo", "bar"];$array->length()       // == count($array)             == 3$array->join(" ")      // == implode(" ", $array)      == "test foo bar"$array->slice(1, 2)    // == array_slice($array, 1, 2) == ["foo", "bar"]$array->flip()         // == array_flip($array)        == ["test" => 0, "foo" => 1, "bar" => 2]

這裡的$str僅僅是一個普通的字串,$array僅僅是一個普通的數組——他們不是對象。我們所做的僅僅是給他們了一點兒比較像對象的特性,讓他們可以調用方法。

注意上面的特性可不是遙不可及,而是現在已經存在了。PHP擴充scalar_objects允許你在原生PHP類型上面定義物件導向的方法。

原生類型引入對象式的調用方法也帶來了許多好處,我會在下面列出來:

一個讓API進一步讓簡潔的機會

聽到最多的最常見的關於PHP的抱怨很可能是標準庫中函數的前後矛盾、命名不清楚,還有前後不一致、順序混亂的參數。一些典型的例子:

//不同的函數命名規範strposstr_replace//很不清晰的命名strcspnstrpbrk//顛倒的參數順序strpos($haystack, $needle)array_search($needle, $haystack)

然而這些問題通常被過分強調了(我們有整合式開發環境),很難否定狀況還是相當好的。還應當指出的是,許多函數表現出的問題遠遠超過了詭異的名字這個問題。通常邊緣情況的行為是沒有被完全考慮的,因而有了在調用代碼中對它們進行特殊處理的需要。(對字串函數來說,邊緣情況通常包括Null 字元串或者超出字串範圍的位移量。)

一個一般的建議是在PHP6中實現巨大數量的別名,用來統一函數名稱和參數順序。因此我們將會有string\pos(),string\reoplace(),string\complement_span()或者類似的。個人而言(而且這看起來像是對許多php-src開發人員的意見)這些對我的意義很小。現在的這些函數名已經深深的根植在PHP程式員的記憶中了,對它們實現一些不重要的裝飾性的改變看起來好像不值得。

原生類型物件導向API的引入另一方面也提供了一個API重新設計的契機(原文:The introduction of an OO API for primitive types on the other hand offers an opportunity of an API redesign as a side effect of switching to a new paradigm)。這也是真正讓我們從零開始,不需要考慮舊的API的期望輸出。兩個例子:

  • 我非常希望有$string->split($delimiter)$array->join($delimiter)這樣的方法,這些是函數功能是普遍接受的名字(與explodeimplode不同)。另一方面如果有一個string\split($delimiter)方法有這樣的行為我會感到非常反感,因為已經存在的str_split函數所做的是完全不同的(分組)。
  • 我理所當然的喜歡新的對錯誤報表使用異常的API,由於這是一個物件導向的API,那是自動給定的,異常當然也是和重新命名的API一起使用,然而這違背了現在所有的程式函數對錯誤處理使用警告的慣例,這不是一成不變的,但這確實是我想避免的一個爭議點:)

這是我實現原生類型物件導向API的主要動機:從零開始,允許我們實現一套合理的API設計。當然了,這個改動的所有好處不知這些。物件導向的文法提供了許多更深層的好處,將在下面討論。

提高了可讀性

程式式的調用一般沒有鏈式調用好。考慮下面的例子:

$output = array_map(function($value) {    return $value * 42;}, array_filter($input, function($value) {    return $value > 10;});

咋一看,哪個是arraay_maparray_filter各自的使用?(原文:what are array_map and array_filter applied to? )他們調用的順序是什嗎?變數$input隱藏在兩個閉包之間,函數的書寫順序也和他們實際調用的順序相反。現在同樣的例子使用物件導向的文法:

$output = $input->filter(function($value){return $value > 10;})->map(function($value){return $value * 42;});

我敢說使用這種方式,操作的順序(先filtermap)和初始輸入數組$input更加明顯。

這個例子明顯有人為拼湊的感覺,因為array_maparray_filter是函數參數順序顛倒的另外一個例子(這就是為什麼輸入數組在中間)。再看另外一個輸入參數在同一個位置的例子(來自實際的代碼):

substr(strstr(rtrim($className, '-'), '\\', '_'), 15);

在這個例子中,最後面是一連串的額外的參數'_'), '\\', '_'), 15,,很難把這些參數和應用的函數對應起來。把這個和使用物件導向方法的版本做個比較:

$className->trimRight('_')->replace('\\', '_')->slice(15);

這次函數運算和他們的參數緊密的聯絡在了一起,而且方法的調用和他們的執行順序相匹配。

另一個來自這種文法的可讀性的好處是needle/haystack不明確問題。別名通過引入統一的參數順序規範讓我們解決了這個問題,使用物件導向的API這個問題基本不存在了。

$string->contains($otherString);$string->contains($someValue);$string->indexOf($otherString);$string->indexOf($someValue);

這裡哪個部分應用了哪個規則的困惑不複存在了。

多態

目前PHP有提供Contable介面,這個介面可以通過類實現自訂的輸出函數count($obj)。為什麼需要這個?因為我們PHP的函數沒有多態。然而我們方法中確實需要多態:

如果數組實現$array->count()作為一個方法,實際上代碼是不會在意$array是不是一個數組的,他可以是其他任何類型的實現count()方法的對象,這基本上給了我們Countable的所有行為,~(原文:This basically gives us the same behavior as Countable, just without the engine hackery it requires.)

這也是一個很一般的解決方案。舉個例子,你可以實現一個實現所有字串類型方法的UnicodeString類,然後可以隨便的使用正常的字串和UnicodeStrings。好吧,至少這還是理論。這很明顯局限於那些字串方法的使用,而且調用級聯操作的時候會返回錯誤(原文:This would obviously only work as long as the usage is limited to just the string methods, and would fail once the concatenation operator is employed)(運算子多載目前只支援核心中的類)。

我仍然有強大的信念希望這個清晰起來,同樣應用在數組等上面。通過繼承相同的介面,你可有一個和數組行為方式相同的SplFixedArray。(原文:you could have an SplFixedArray behave the same way as an array, by implementing the same interface.)

既然我們已經總結了這個方法的一些好處,讓我們也來看看它的問題:

鬆散的類型

摘抄自Anthony發表的部落格:

標量不是對象,但更重要的是他們不是任何類型。PHP依賴一個類型系統,字串和數字是同一個。系統中許多的靈活性基於任何標量可以很容易的轉換為其他標量。

更重要的是,由於鬆散的類型系統,你不可能在任何時候知道一個變數的類型是哪個。你可以說出你想要他是什麼類型,但你不知道他內在的類型是什麼。Even with casting or scalar type hinting it isn’t a perfect situation since there are cases where types can still change.

為了闡明這個問題,考慮下面的例子:

$num = 123456789;$sumOfDigits = array_sum(str_split($num));

這裡$num被作為一個字串數字,被str_split切分後使用array_sum求和。現在試試同樣效果的物件導向方法調用:

$num = 123456789;$sumOfDigits = $num->chunk()->sum();

這裡字串的cheunk()方法被數字來調用。會發生什嗎??Anthony建議這樣解決:

這意味著所有的標量運算將必然需要對應的標量類型。這將導致需要一個標量有所有的數學方法的物件模型,當然包括所有的字串方法。真是一個噩夢。。。。。

就像引言中所說的那樣,這絕不是一個可接受的解決方案。然而我想我們可以絕對僥倖的逃脫僅僅在那種情況的時候拋出一個錯誤(異常!)。為瞭解釋為什麼這種方法是可行的,讓我們看看PHP可以擁有哪些類型。

PHP中的原生類型

除了對象之外,PHP有下面的變數類型:

nullboolintfloatstringarrayresource

現在,我們考慮下上面這些裡面的哪些會需要物件導向的方法:我們首先去掉resource,然後在剩下的裡面看。nullbool明顯不需要物件導向的方法,除非你想進行像$bool->invert()這樣無聊的轉換。

絕大多數的數學函數使用物件導向的方法也不是很合適。考慮下面幾個例子:

log($n)$n->log()sqrt($n)$n->sqrt()acosh($n)$n->acosh()

我想你會同意數學函數可讀性比函數符號的形式更好。當然存在少許的物件導向方法你可以適當的應用數字類型,比如說$num->format(10)讀起來相當的不錯。然而,關於這裡,對於一個物件導向的數字API不是真正的需要,只有少量的函數你可能需要。(再者來說目前的數學API在命名方面沒有太多的問題,而且數學操作相關的命名相當的標準。)

現在剩給我們的只有字串和數組了,我們已經看到這兩種類型有許多很棒的API。但關於鬆散類型的問題我們所有的必須要做的有哪些?下面是重要的幾點:

我們經常性的把字串視為數位時候(例如來自HTTP或者DB),這樣反過來是不對的:直接將數字作為字串非常少見。舉個栗子,下面的代碼將讓我們感到困惑:

strpos(54321, 32, 1);

這樣將數字視為字串是一個很怪異的操作,這種情況也ok啊,只需要強制轉換一次就好了。使用原來的求和數位例子:

$num = 123456789;$sumOfDigits = ((string) $num)->chunk()->sum();

現在我們弄明白了,是的,我們確實想將數字視為字串。對我來說,這樣來處理想這樣使用這種技術的地方是可以接受的。

對於數組情況就更簡單了:他不會出現講一個數組操作視為一個其他不是數群組類型的操作。

另一方面可以通過標量類型提示改善這個問題(我完全認為在PHP所有的版本都存在——最令人尷尬的問題是現在仍然沒有(原文:which I totally assume to be present in any PHP version this gets in - really embarrassing that we still don’t have them))。如果內類型提示string,你擷取輸入的字串將會是一個字串(即使傳遞給函數的不是——這取決於類型提示實現的具體內容)。

當然了,我並不是暗示這裡沒有一點問題。由於錯誤的函數設計,有時候可能會發生未知的類型潛入代碼中,例如substr($str, strlen($str))將自作聰明的返回bool(false)而不是string(0) ""。(不過,這個問題僅有substr存在。物件導向的API不存在那個問題,所以你碰不到那個問題。)

對象傳遞語義

除了若類型的問題之外,還有原生類型偽方法的一個語義的問題:PHP中的對象和其他類型相比有不同的傳遞語義(某種程度上和引用類似)。如果我們允許字串和數組進行物件導向的方法調用,他們看起來會和對象很像,那樣的話有些人可能期望他們有對象作為參數的傳遞語義。這個問題在在字串和數組中都存在:

function change($arg) {echo $arg->length();//$arg looks like object$arg[0] = 'x';//但是沒有對象的傳遞語義}$str = 'foo';change($str);//$str stays the same$array = ['foo', 'o', 'o'];change($array);//$array stays the same

我們當然將會改變傳遞語義。首先,在我看來通過值傳遞來傳遞像數組這種大的資料結構是一個相當low的想法,我更願意他們像對象一樣傳遞。然而,那將是一個相當大的突破性的向後相容,並且那將不易於自動的重構(原文:However, that would be a pretty big backwards-compatibility break and one that’s not easy to refactor automatically)(至少我猜想是這樣的。我沒有做實驗去探索這樣一個改變帶來的實際影響)。另一方面,對於字串通過對象方式傳遞參數將是一個災難,除非我們讓字串同一時間完全的不可變,放棄目前所有的局部變數的可變性(我個人發現非常的容易——去嘗試改變一個Python字串的一個位元組)。

我不知道是否有好的方法去解決這個預期的問題,除了在我們的文檔中強調字串和數組在物件導向的方法中僅僅視作”偽對像“,不是真正的對象。

這個問題可以被擴充到其他的對象相關的特性。例如你可將會問像$string instanceof string這樣的是否正確。我還沒有確定整個事情的完整走向。也許嚴格堅持僅僅在物件導向的方法使用,然後強調他們不是真正的對象會好一點。然而也許支援物件導向系統的更深層次的特性也會好點。這個觀點應該進一步的思考下。

目前的狀態

總而言之,這個方法有許多的問題,但我不認為他們特別重要。同時這個提供了一個很好的機會為我們的基本類型引入簡潔明了的APIs,提高代碼執行操作時候的可讀性(可寫性)。

那麼這個想法目前的狀態是什麼呢?從我收集的內容來看,內部的人們不是特別的反對這個做法,但更願意重新命名所有的函數。主要的沒有推進這個的原因是API提議~

為了這個目的,我建立了scalar_objects擴充,作為一個PHP擴充實現了這個功能。它允許你註冊一個處理各自的原生類型的方法調用的類。看一個例子:

class StringHandler {public function length(){return strlen($this);}public function contains($str){return false !== strpos($this, $str);}}register_primitive_type_handler('string', 'StringHandler');$str = 'foo bar baz';var_dump($str->legth());//int(11)var_dump($str->contains('bar'));//bool(true)var_dump($str->contains('hello'));//bool(false)

不久前,我開始了一個string handler包括一個API說明的工作,但一直沒有真正的完成哪個項目(我希望我在不久找到一些重新開始他的動機)。當然也有許多其他項目在為實現這樣的APIs而努力。

嗯,這是我想在PHP6中所看到的其中一個改進。我也許會為我的那個方向的計劃寫另外一篇文章。

引用

原文連結 : http://nikic.github.io/2014/03/14/Methods-on-primitive-types-in-PHP.html

HOOKPHP : http://netsecurity.51cto.com/art/201407/446430.htm

以上就介紹了PHP中原生類型的方法,包括了方面的內容,希望對PHP教程有興趣的朋友有所協助。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.