我剛開始升級我的程式碼以相容 php 8.1。我有很多程式碼片段,我將潛在的空值傳遞給內部函數。
if (strlen($row) > 0) { ... }
其中 $row 來自可能具有空值的來源(例如查詢)。這可能會產生棄用警告;在這種情況下:
已棄用:strlen():已棄用將 null 傳遞給字串類型的參數 #1 ($string)
我正在尋找最簡單、最省時的方法來處理升級此程式碼,例如修復可以進行全域搜尋和替換的地方。似乎對我傳遞給內部函數的變數進行類型轉換,無需更改功能。
error_reporting(E_ALL); $row = null; if (strlen((string) $row) > 0) { ... }
除了以這種方式編碼的道德方面之外,這種內部功能方法是否存在問題?有沒有更好的方法(除了完全重寫程式碼並以不同的方式處理空值之外)?我更喜歡這個向後相容 v7.4 的解決方案,儘管我可能會相容於 8.0。
我知道我的使用者定義函數還有其他選擇。
P粉4364105862023-11-01 11:49:53
回答有關「處理升級此程式碼的最簡單、最省時的方法」的問題。
簡而言之,你不能。
首先,一些背景...
大約15% 的開發者使用strict_types=1
,所以您屬於大多數不這樣做的開發者中。
您現在可以忽略這個問題(棄用),但是 PHP 9.0 會透過使其成為致命類型錯誤而導致很多問題。
也就是說,您仍然可以使用 NULL 連接字串:
$name = NULL; $a = 'Hi ' . $name;
您仍然可以將 NULL 與空字串進行比較:
if ('' == NULL) { }
並且您仍然可以使用 NULL 進行計算(它仍然被視為 0):
var_dump(3 + '5' + NULL); // Fine, int(8) var_dump(NULL / 6); // Fine, int(0)
你仍然可以列印/回顯 NULL:
print(NULL); echo NULL;
您仍然可以將 NULL 傳遞到 sprintf()
中,並使用 %s
將其強制為空字串,例如
sprintf('%s', NULL);
您仍然可以強制其他值(遵循規則),例如
strlen(15); htmlspecialchars(1.2); setcookie('c', false);
從那時起,NULL 強制就這樣運作了,我假設從一開始,也有記錄:
無論如何,要修復...第一部分,它會嘗試尋找您需要更新的程式碼。
只要可以將 NULL 傳遞給這些函數參數之一,就會發生這種情況。
至少有 335 受此影響的參數。
還有一個額外的104,它們是有點可疑;和558 其中NULL 有問題,你應該在哪裡修復這些問題,例如define(NULL, '值')
。
Psalm 是我能找到的唯一能夠對此提供幫助的工具。
詩篇需要處於非常高的檢查等級(1、2 或 3)。
並且您不能使用基準來忽略問題(開發人員在現有專案中引入靜態分析的技術,因此它只檢查新的/編輯過的程式碼)。
如果您之前沒有使用過靜態分析工具(不用擔心,建議僅使用33% 的開發者這樣做);然後預計會花費大量時間修改程式碼(從第8 級開始,最寬鬆,然後慢慢提高)。
我無法使用PHPStan、Rector、PHP CodeSniffer、PHP CS Fixer 或PHPCompatibility 來查找這些問題(來源)。
找到每個問題後,第二部分就是編輯。
最不可能引起問題的地方是更換水槽,例如
example_function(strval($name)); example_function((string) $name); example_function($name ?? '');
或者,您可以嘗試追溯到變數的來源,並嘗試先封鎖將其設為 NULL。
以下是一些非常常見的 NULL 來源:
$search = (isset($_GET['q']) ? $_GET['q'] : NULL); $search = ($_GET['q'] ?? NULL); // Fairly common (since PHP 7) $search = filter_input(INPUT_GET, 'q'); $search = $request->input('q'); // Laravel $search = $request->get('q'); // Symfony $search = $this->request->getQuery('q'); // CakePHP $search = $request->getGet('q'); // CodeIgniter $value = mysqli_fetch_row($result); $value = json_decode($json); // Invalid JSON, or nesting limit. $value = array_pop($empty_array);
其中一些函數需要第二個參數來指定預設值,或者您可以提前使用strval()
...但要小心,您的程式碼可能會通過< code>($a = == NULL),而且您不想破壞它。
許多開發人員不會意識到他們的某些變數可以包含NULL - 例如期望 (他們創建的)始終提交所有輸入欄位;由於網路問題、瀏覽器擴充功能、用戶在瀏覽器中編輯DOM/URL 等,這種情況可能不會發生。
一年中的大部分時間我都在研究這個問題。
我開始寫兩個 RFC 來嘗試解決這個問題。第一個是更新一些函數以接受NULL(這並不理想,因為它讓使用strict_types 的開發人員感到不安); 第二個RFC 是允許NULL 在這種情況下繼續被強制. .....但我沒有不要將其付諸投票,因為我剛剛收到了大量負面反饋,並且我不希望將來引用該拒絕來解釋為什麼此問題無法解決(而最初的更改幾乎沒有被討論,這一個)。
似乎 NULL 的處理方式有所不同,因為它從未被視為「標量值」 - 我認為許多開發人員並不關心這種區別,但它不時會出現。
與我合作過的開發人員中,大多數人都忽略了這個問題(希望稍後能解決它,這可能不是最好的主意);例如
function ignore_null_coercion($errno, $errstr) { // https://github.com/php/php-src/blob/012ef7912a8a0bb7d11b2dc8d108cc859c51e8d7/Zend/zend_API.c#L458 if ($errno === E_DEPRECATED && preg_match('/Passing null to parameter #.* of type .* is deprecated/', $errstr)) { return true; } return false; } set_error_handler('ignore_null_coercion', E_DEPRECATED);
有一個團隊試圖將 strval()
套用到所有事情,例如修剪(strval($search))
。但一年多後他們仍然發現問題(他們表示使用 8.1 alpha 1 進行測試)。
我正在考慮的另一個選擇是創建一個庫,在命名空間下將所有這些 ~335 個函數重新定義為可為空;例如
namespace allow_null_coercion; function strlen(?string $string): int { return \strlen(\strval($string)); }
然後開發人員將包含該庫,並自行使用命名空間:
namespace allow_null_coercion; $search = $request->input('q'); // Could return NULL // ... echo strlen($search);
P粉0870748972023-11-01 09:34:02
如果您明確嘗試處理 null
的情況,那麼稍微乾淨一點的修復方法是 strlen($row ?? '')
使用「null合併運算子」。
在大多數情況下,兩者可能是等效的,但在strict_types=1
生效的情況下,如果值是可以轉換為字串的其他類型,則它們的行為會有所不同:
declare(strict_types=1); $row = 42; echo strlen($row); // TypeError: must be of type string, int given echo strlen((string) $row); // Succeeds, outputting '2' echo strlen($row ?? ''); // TypeError: must be of type string, int given
另一方面,請注意??
運算子是基於isset
,而不是=== null
,因此未定義變數的行為會有所不同:
declare(strict_types=1); $row = []; echo strlen($row['no_such_key']); // Warning: Undefined array key; TypeError: must be of type string, null given echo strlen((string) $row['no_such_key']); // Warning: Undefined array key; outputs '0' echo strlen($row['no_such_key'] ?? ''); // No warning, just outputs '0'
如果您關心這種情況,與舊行為最直接等效的程式碼會更加冗長:
echo strlen($row === null ? '' : $row);