介紹
如果您是PHP開發人員,您可能在某些PHP檔的開頭看到過
declare(strict_types=1)
語句。
我第一次看到這個聲明時,我不知道它是做什麽的。我以為這是某種註釋,或者是我之前的舊PHP語法,但我錯了(大錯特錯!)。
在這篇文章中,我們將介紹什麽是
declare(strict_types=1)
,以及它如何幫助您提高PHP程式碼的型別安全性。
declare(strict_types=1) 是什麽?
declare(strict_types=1)
是一個啟用PHP嚴格模式並在PHP應用程式中強制嚴格型別的語句。
它是在PHP 7.0中添加的,當時型別聲明系統首次在PHP中實作。這意味著它可以在PHP 8計畫中使用,因此您可以開始在程式碼中充分利用嚴格型別。
當你使用這個語句時,PHP會對函式的參數和返回型別進行嚴格的型別檢查。這意味著如果一個函式需要某種型別的參數或返回值,如果使用了錯誤的型別,PHP將丟擲錯誤。這也適用於具有指定型別提示和返回型別的PHP閉包和箭頭函式。
讓我們舉一個不使用
declare(strict_types=1)
的簡單例子:
functionadd(int $a, int $b): int
{
return $a + $b;
}
現在假設我們用字串參數呼叫這個函式:
echo add('1', '2');
// Output:
// 3
PHP會很高興地將字串參數轉換為整數並返回結果
3
。
在某些情況下,您可能完全不介意這種行為。但它可能會產生一些您沒有預料到的意外後果,並可能導致應用程式中的錯誤。
然而,讓我們假設我們想在這個例子中使用
declare(strict_types=1)
。我們可以透過在檔頂部添加以下語句來實作這一點:
declare(strict_types=1);
function add(int $a, int $b): int
{
return$a + $b;
}
現在,如果我們用字串參數呼叫
add
函式,PHP將丟擲一個錯誤:
echo add('1', '2');
// Output:
// Fatal error: Uncaught TypeError: Argument 1 passed to add() must be of the type int, string given
正如我們在這裏看到的,PHP丟擲了一個錯誤,因為
add
函式期望傳遞整數,但卻接收到了字串。
類似地,如果啟用了嚴格的型別檢查,並且我們試圖從方法返回錯誤的數據型別,PHP也會丟擲錯誤。例如,假設我們的
add
函式現在接受浮點數而不是整數,並且我們沒有啟用嚴格的型別檢查:
function add(float$a, float$b): int
{
return$a + $b;
}
我們可以這樣呼叫函式:
echo add(1.25, 2.25);
// Output:
// 3
你發現輸出中的問題了嗎?
我們應該得到的答案是
3.5
。然而,因為我們已經將返回型別定義為
int
,所以我們已經將浮點數(應該返回的)轉換為整數,並失去了精度。可以想象,這可能會在我們應用程式的其他部份導致一些問題,我們正在使用這個結果,並且可能需要精度。
現在讓我們透過使用
declare(strict_types=1)
來解決這個問題:
declare(strict_types=1);
function add(float$a, float$b): int
{
return$a + $b;
}
我們可以這樣呼叫函式:
echo add(1.25, 2.25);
// Output:
// Fatal error: Uncaught TypeError: add(): Return value must be of type int, float returned
正如我們所看到的,透過啟用嚴格的型別檢查,我們可以發現函式沒有返回與返回型別聲明匹配的正確數據型別。這很好,因為它可以突出顯示我們程式碼中可能存在的錯誤,而我們並不知道。然後,我們可以采取必要的步驟:
如果返回型別不正確,請更新它們
如果型別提示不正確,請更新型別提示
如果數據型別不正確,則更新函式體以返回正確的數據型別
修復呼叫函式的程式碼中可能向其傳遞錯誤數據型別的任何錯誤
我應該使用
declare(strict_types=1)
嗎?
我個人認為,在所有的PHP檔中使用
declare(strict_types=1)
是一個好主意。我曾經認為僅僅有型別提示和返回型別就足以確保傳遞正確的數據型別,但我現在改變了主意。當我使用
declare(strict_types=1)
時,我對我的程式碼更有信心,並且由於使用它而發現了一些bug(特別是當將它添加到舊程式碼庫時)。
由於PHP是一種動態型別的語言(而不是嚴格型別的語言),這意味著如果你不想的話,你根本不需要指定任何返回型別或型別提示。相反,PHP將在執行時為您確定型別。然而,即使有可能這樣做,我還是強烈建議不要這樣做。如果你不能在程式碼中使用嚴格型別(無論出於什麽原因),我仍然建議使用型別提示和返回型別作為最低限度來提高PHP程式碼品質。
自從了解它以來,我習慣在我建立的每個新PHP檔中使用它。事實上,我更新了PhpStorm設定中的所有樣版,以便它自動包含在我建立的每個檔的頂部。例如,下面是建立一個新的PHP類時使用的樣版:
<?php
declare(strict_types=1);
#parse("PHP File Header.php")
#if (${NAMESPACE})
namespace ${NAMESPACE};
#end
class ${NAME} {
}
這真的很方便,因為它鼓勵我繼續使用
declare(strict_types=1)
,而不需要在建立檔後進行任何手動更改(我肯定會忘記這樣做!)。
對於我的任何Laravel閱讀器,您還可以在執行Artisan命令(如
php artisan make:controller
)時釋出用於建立PHP檔的存根。透過釋出存根,您可以編輯它們並將
declare(strict_types=1)
添加到頂部。這意味著您使用Artisan命令建立的檔將在已啟用更嚴格型別安全的情況下建立。
當然,如果您打算對現有檔添加更嚴格的型別檢查,我強烈建議您首先要有一個高品質的測試套件。您的PHP程式碼可能允許傳遞不正確的數據型別而不引發任何錯誤。但是,透過啟用嚴格的型別檢查,您的程式碼將變得不那麽寬容,並可能開始丟擲錯誤。這可能會導致應用程式以使用者意想不到的方式中斷。
您可能還會發現需要重構一些程式碼,使其與
declare(strict_types=1)
相容。但我不認為這是件壞事。相反,我認為這是一個提高程式碼品質的機會。
為了幫助您將
declare(strict_types=1)
添加到程式碼中,您可能需要使用PHPStan之類的工具,它可以為您收集這些型別不匹配。