將現有程式碼庫更新到 php 8.1:處理具有 Null 值的不可空內部函數參數
P粉344355715
P粉344355715 2023-10-31 20:01:48
0
2
803

我剛開始升級我的程式碼以相容 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粉344355715
P粉344355715

全部回覆(2)
P粉436410586

回答有關「處理升級此程式碼的最簡單、最省時的方法」的問題。

簡而言之,你不能。


首先,一些背景...

大約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 強制就這樣運作了,我假設從一開始,也有記錄:

  • To String:「null 總是轉換為空字串。」
  • 轉為整數:「null 總是轉換為零(0)。」
  • 浮動:「對於其他類型的值,轉換的方式是先將值轉換為 int,然後再轉換為 float」
  • 轉為布林值:「轉換為布林值時,以下值被視為 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()...但要小心,您的程式碼可能會通過($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粉087074897

如果您明確嘗試處理 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);
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板