PHP 中的泛型。我知道我想要的就是這個。我知道很多開發者都想要這個使用這個型別。另一方面,也可能有很大規模的一群 PHP 程式設計師,不知道泛型是什麼,或者認為他們為什麼要有這個型別。
我將在這個部落格上做一個關於泛型和 PHP 的系列文章。讓我們從頭開始,很快我們就會找到更復雜的話題。我們將討論什麼是泛型,為什麼 PHP 不支援它們,未來可能發生什麼。
讓我們開始吧。
每種程式語言都有某種型別的系統。有些語言的實現非常嚴格,而其他語言 ——PHP 屬於這一類 —— 則要寬鬆得多
現在,使用型別系統的原因有很多。 最明顯的是型別驗證。
假設我們有一個接受兩個數位、兩個整數的函數; 並對它們進行一些數學運算:
function add($a, $b) { return $a + $b; }
PHP 允許您將任何型別的資料傳遞給該函數,數位、字串、布林值都無所謂。 PHP 將盡最大努力在有意義的時候轉換變數,例如將它們加在一起。
add('1', '2');
但是這些轉換 —— 型別雜耍 —— 通常會導致意想不到的結果,或者說是:錯誤和崩潰。
add([], true); // ?
現在,我們可以手動編寫程式碼來檢查我們的數學加法運算,它將被用於任何給定的輸入
function add($a, $b) { if (!is_int($a) || !is_int($b)) { return null; } return $a + $b; }
或者,我們可以使用 PHPS 內建型別提示–這是我們手動執行操作的內建簡寫:
function add(int $a, int $b): int { return $a + $b; }
PHP 社群中的許多開發人員說他們並不真正關心這些型別提示,因為他們知道自己應該只將整數傳遞給這個函數 - 畢竟是他們自己寫的。
然而,這種推理很快就會瓦解:您通常不是唯一一個在該程式碼庫中工作的人,您還在使用不是您自己編寫的程式碼 - 想想您用 Composer 引入了多少包。因此,雖然這個孤立的範例看起來不是什麼大問題,但是一旦您的程式碼開始增長,型別檢查確實會派上用場。
除此之外,新增型別提示不僅可以防止無效狀態,而且還澄清我們程式設計師需要什麼樣型別的值輸入。定義好型別後通常使您無需閱讀外部檔案,因為函數的大部分功能已經被其型別定義封裝。
IDE 大量使用了這一原則:它們可以告訴程式設計師函數期望什麼樣型別的值的輸入,或者物件上有哪些欄位和方法可用 —— 因為它屬於一個類。IDE 使我們的程式碼編寫效率更高,這在很大程度上是因為它們可以靜態分析我們程式碼庫中的型別提示。
記住這個詞:靜態分析 —— 這在本系列的後面會非常重要。 這意味著程式、IDE 或其他型別的「靜態分析器」可以檢視我們的程式碼,並且在不執行它的情況下告訴我們它是否會工作 —— 至少在某種程度上是這樣。如果我們將一個字串傳遞給我們的只接受整數的函數,我們的 IDE 會告訴我們我們做錯了什麼 —— 這會導致程式在執行時崩潰;但我們的 IDE 無需實際執行程式碼就能告訴我們。
另一方面,型別系統也有其侷限性。 一個常見的例子是「專案列表」:
class Collection extends ArrayObject { public function offsetGet(mixed $key): mixed { /* … */ } public function filter(Closure $fn): self { /* … */ } public function map(Closure $fn): self { /* … */ } }
一個集合有很多方法可以處理任何型別的輸入:迴圈、過濾、對映,等等;集合實現不應該關心它是處理字串還是整數。
但是,讓我們從局外人的角度來看。如果我們想確保一個集合只包含字串,而另一個集合只包含「使用者」物件,會發生什麼。集合本身在迴圈其 items
時並不關心,但我們關心。我們想知道迴圈中的這個專案是使用者還是字串 —— 這是完全不同的。但是如果沒有正確的型別資訊,我們的 IDE 就會在未知情況中執行。
$users = new Collection(); // … foreach ($users as $user) { $user-> // ? }
現在,我們可以為每個集合建立單獨的實現:一個只適用於字串的實現,另一個只適用於 User
物件:
class StringCollection extends Collection { public function offsetGet(mixed $key): string { /* … */ } } class UserCollection extends Collection { public function offsetGet(mixed $key): User { /* … */ } }
但是如果我們需要第三個實現?第四個?也許 10 個或 20 個。管理這些程式碼將會變得非常困難。
這就是泛型的用武之地。
需要澄清的是:PHP 沒有泛型。這是一個大膽的宣告,走了不少彎路,我們將在本系列的後面部分討論這一點。但是現在我可以說我接下來要展示的內容在 PHP 中是沒有的。 但是它存在於其他程式語言中。
許多程式語言允許開發人員在集合類上定義 「泛型」,而不是為每個可能的型別去單獨實現:
class Collection<Type> extends ArrayObject { public function offsetGet(mixed $key): Type { /* … */ } // … }
基本上我們說的是集合類的實現適用於任何型別的輸入,但是當我們建立集合的範例時,我們應該指定一個型別。它是一個泛型實現,需要根據程式設計師的需求來特定:
$users = new Collection<User>(); $slugs = new Collection<string>();
新增型別似乎是一件小事。但這種型別本身就開啟了一個充滿可能性的世界。 我們的 IDE 現在知道了集合中的資料型別,它可以告訴我們是否新增了錯誤型別的項;它可以告訴我們在迭代集合時可以對項執行什麼操作;它可以告訴我們是否將集合傳遞給知道如何處理這些特定項的函數。
雖然我們可以通過手動為我們需要的每種型別實現一個集合,在技術上實現同樣的效果;對於編寫和維護程式碼的開發人員來說,通用實現將是一項重大改進。
那麼,我們為什麼不在 PHP 中使用泛型呢?除了無聊的收藏,我們還能用它們做什麼?我們能為他們增加支援嗎?我們將在這個系列中回答所有這些問題。首先需要澄清的是:我在本系列文章中的目標是教你關於泛型的知識,但同樣重要的是,我想讓大家意識到我們是如何誤解 PHP 的。我想改變這種狀況。
英文原文地址:https://stitcher.io/blog/generics-in-php-1
推薦:《》
以上就是帶你聊聊PHP中的泛型之基礎知識淺析的詳細內容,更多請關注TW511.COM其它相關文章!