我剛開始升級(jí)我的程式碼以相容 php 8.1。我有很多程式碼片段,我將潛在的空值傳遞給內(nèi)部函數(shù)。
if (strlen($row) > 0) { ... }
其中 $row 來(lái)自可能具有空值的來(lái)源(例如查詢)。這可能會(huì)產(chǎn)生棄用警告;在這種情況下:
已棄用:strlen():已棄用將 null 傳遞給字串類型的參數(shù) #1 ($string)
我正在尋找最簡(jiǎn)單、最省時(shí)的方法來(lái)處理升級(jí)此程式碼,例如修復(fù)可以進(jìn)行全域搜尋和替換的地方。似乎對(duì)我傳遞給內(nèi)部函數(shù)的變數(shù)進(jìn)行類型轉(zhuǎn)換,無(wú)需更改功能。
error_reporting(E_ALL); $row = null; if (strlen((string) $row) > 0) { ... }
除了以這種方式編碼的道德方面之外,這種內(nèi)部功能方法是否存在問(wèn)題?有沒(méi)有更好的方法(除了完全重寫程式碼並以不同的方式處理空值之外)?我更喜歡這個(gè)向後相容 v7.4 的解決方案,儘管我可能會(huì)相容於 8.0。
我知道我的使用者定義函數(shù)還有其他選擇。
回答有關(guān)「處理升級(jí)此程式碼的最簡(jiǎn)單、最省時(shí)的方法」的問(wèn)題。
簡(jiǎn)而言之,你不能。
首先,一些背景...
大約15% 的開發(fā)者使用strict_types=1
,所以您屬於大多數(shù)不這樣做的開發(fā)者中。
您現(xiàn)在可以忽略這個(gè)問(wèn)題(棄用),但是 PHP 9.0 會(huì)透過(guò)使其成為致命類型錯(cuò)誤而導(dǎo)致很多問(wèn)題。
也就是說(shuō),您仍然可以使用 NULL 連接字串:
$name = NULL; $a = 'Hi ' . $name;
您仍然可以將 NULL 與空字串進(jìn)行比較:
if ('' == NULL) { }
並且您仍然可以使用 NULL 進(jìn)行計(jì)算(它仍然被視為 0):
var_dump(3 + '5' + NULL); // Fine, int(8) var_dump(NULL / 6); // Fine, int(0)
你仍然可以列印/回顯 NULL:
print(NULL); echo NULL;
您仍然可以將 NULL 傳遞到 sprintf()
中,並使用 %s
將其強(qiáng)制為空字串,例如
sprintf('%s', NULL);
您仍然可以強(qiáng)制其他值(遵循規(guī)則),例如
strlen(15); htmlspecialchars(1.2); setcookie('c', false);
從那時(shí)起,NULL 強(qiáng)制就這樣運(yùn)作了,我假設(shè)從一開始,也有記錄:
無(wú)論如何,要修復(fù)...第一部分,它會(huì)嘗試尋找您需要更新的程式碼。
只要可以將 NULL 傳遞給這些函數(shù)參數(shù)之一,就會(huì)發(fā)生這種情況。
至少有 335 受此影響的參數(shù)。
還有一個(gè)額外的104,它們是有點(diǎn)可疑;和558 其中NULL 有問(wèn)題,你應(yīng)該在哪裡修復(fù)這些問(wèn)題,例如define(NULL, '值')
。
Psalm 是我能找到的唯一能夠?qū)Υ颂峁椭墓ぞ摺?
詩(shī)篇需要處?kù)斗浅8叩臋z查等級(jí)(1、2 或 3)。
並且您不能使用基準(zhǔn)來(lái)忽略問(wèn)題(開發(fā)人員在現(xiàn)有專案中引入靜態(tài)分析的技術(shù),因此它只檢查新的/編輯過(guò)的程式碼)。
如果您之前沒(méi)有使用過(guò)靜態(tài)分析工具(不用擔(dān)心,建議僅使用33% 的開發(fā)者這樣做);然後預(yù)計(jì)會(huì)花費(fèi)大量時(shí)間修改程式碼(從第8 級(jí)開始,最寬鬆,然後慢慢提高)。
我無(wú)法使用PHPStan、Rector、PHP CodeSniffer、PHP CS Fixer 或PHPCompatibility 來(lái)查找這些問(wèn)題(來(lái)源)。
找到每個(gè)問(wèn)題後,第二部分就是編輯。
最不可能引起問(wèn)題的地方是更換水槽,例如
example_function(strval($name)); example_function((string) $name); example_function($name ?? '');
或者,您可以嘗試追溯到變數(shù)的來(lái)源,並嘗試先封鎖將其設(shè)為 NULL。
以下是一些非常常見的 NULL 來(lái)源:
$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);
其中一些函數(shù)需要第二個(gè)參數(shù)來(lái)指定預(yù)設(shè)值,或者您可以提前使用strval()
...但要小心,您的程式碼可能會(huì)通過(guò)($a = == NULL),而且您不想破壞它。
許多開發(fā)人員不會(huì)意識(shí)到他們的某些變數(shù)可以包含NULL - 例如期望
(他們創(chuàng)建的)始終提交所有輸入欄位;由於網(wǎng)路問(wèn)題、瀏覽器擴(kuò)充功能、用戶在瀏覽器中編輯DOM/URL 等,這種情況可能不會(huì)發(fā)生。
一年中的大部分時(shí)間我都在研究這個(gè)問(wèn)題。
我開始寫兩個(gè) RFC 來(lái)嘗試解決這個(gè)問(wèn)題。第一個(gè)是更新一些函數(shù)以接受NULL(這並不理想,因?yàn)樗屖褂胹trict_types 的開發(fā)人員感到不安); 第二個(gè)RFC 是允許NULL 在這種情況下繼續(xù)被強(qiáng)制. .....但我沒(méi)有不要將其付諸投票,因?yàn)槲覄倓偸盏搅舜罅控?fù)面反饋,並且我不希望將來(lái)引用該拒絕來(lái)解釋為什麼此問(wèn)題無(wú)法解決(而最初的更改幾乎沒(méi)有被討論,這一個(gè))。
似乎 NULL 的處理方式有所不同,因?yàn)樗鼜奈幢灰暈椤笜?biāo)量值」 - 我認(rèn)為許多開發(fā)人員並不關(guān)心這種區(qū)別,但它不時(shí)會(huì)出現(xiàn)。
與我合作過(guò)的開發(fā)人員中,大多數(shù)人都忽略了這個(gè)問(wèn)題(希望稍後能解決它,這可能不是最好的主意);例如
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);
有一個(gè)團(tuán)隊(duì)試圖將 strval()
套用到所有事情,例如修剪(strval($search))
。但一年多後他們?nèi)匀话l(fā)現(xiàn)問(wèn)題(他們表示使用 8.1 alpha 1 進(jìn)行測(cè)試)。
我正在考慮的另一個(gè)選擇是創(chuàng)建一個(gè)庫(kù),在命名空間下將所有這些 ~335 個(gè)函數(shù)重新定義為可為空;例如
namespace allow_null_coercion; function strlen(?string $string): int { return \strlen(\strval($string)); }
然後開發(fā)人員將包含該庫(kù),並自行使用命名空間:
namespace allow_null_coercion; $search = $request->input('q'); // Could return NULL // ... echo strlen($search);
如果您明確嘗試處理 null
的情況,那麼稍微乾淨(jìng)一點(diǎn)的修復(fù)方法是 strlen($row ?? '')
使用「null合併運(yùn)算子」。
在大多數(shù)情況下,兩者可能是等效的,但在strict_types=1
生效的情況下,如果值是可以轉(zhuǎn)換為字串的其他類型,則它們的行為會(huì)有所不同:
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
另一方面,請(qǐng)注意??
運(yùn)算子是基於isset
,而不是=== null
,因此未定義變數(shù)的行為會(huì)有所不同:
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'
如果您關(guān)心這種情況,與舊行為最直接等效的程式碼會(huì)更加冗長(zhǎng):
echo strlen($row === null ? '' : $row);