示例倉庫
github.com/hyperf/hyperf 不要忘了給我一個 Star,謝謝
什麼是 Optional
先讓我們看一段 NodeJS
的程式碼
var obj = null;
console.log(obj?.id);
上述程式碼會輸出 undefined
,如果去掉中間的 ?
,則會丟擲一個 TypeError
console.log(obj.id);
^
TypeError: Cannot read property 'id' of null
而 Optional
就是 PHP
中的一個封裝
Laravel 中的實現
我們可以直接在 Laravel
框架中執行以下程式碼
<?php
dump(optional(null)->id);
會輸出 null
但當我們仔細看一下原始碼,其實實現是有坑的,按照正常的設計我們編寫以下程式碼進行測試
$obj = (object)['id' => 1];
dump(isset(optional($obj)->id)); // true
dump(optional($obj)->id); // 1
dump(isset(optional($obj)['id'])); // false
dump(optional($obj)['id']); // false
$obj = ['id' => 1];
dump(isset(optional($obj)->id)); // true
dump(optional($obj)->id); // null
dump(isset(optional($obj)['id'])); // true
dump(optional($obj)['id']); // 1
我們會發現當入參是陣列的時候,我判斷當前是否存在 id
,結果是存在,但去拿值的時候,卻是 null
然後讓我們看一下原始碼,以下只展示相關的程式碼片段。
<?php
namespace Illuminate\Support;
use ArrayAccess;
use ArrayObject;
class Optional implements ArrayAccess
{
/**
* Dynamically access a property on the underlying object.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
if (is_object($this->value)) {
return $this->value->{$key} ?? null;
}
}
/**
* Dynamically check a property exists on the underlying object.
*
* @param mixed $name
* @return bool
*/
public function __isset($name)
{
if (is_object($this->value)) {
return isset($this->value->{$name});
}
if (is_array($this->value) || $this->value instanceof ArrayObject) {
return isset($this->value[$name]);
}
return false;
}
}
可見,在判斷是否存在成員變數和獲取成員變數的邏輯完全不一致。這才導致了這個問題。
這段程式碼,是後來其他人提交上去的 PR,所以我猜測,一開始的設計是,object 是 object ,array 是 array。二者是不能混用的,所以下面這段程式碼其實不應該被新增進來。
if (is_array($this->value) || $this->value instanceof ArrayObject) {
return isset($this->value[$name]);
}
而在官方的單元測試中,__isset
的單測對 array
的情況已經覆蓋到了,而 __get
也是一樣,這就導致無論是刪除這段程式碼,還是新增這段程式碼到 __get
上,都會導致框架 BC
。
相關 PR
所以只能希望 Laravel 8.0
會修改這個問題了。
Hyperf 中的實現
Hyperf 框架對這麼好用的東西已經做了移植,並解決了這個問題。
我們在 Hyperf 框架中編寫以下測試
$obj = (object)['id' => 1];
dump(isset(optional($obj)->id));
dump(optional($obj)->id);
dump(isset(optional($obj)['id']));
dump(optional($obj)['id']);
$obj = ['id' => 1];
dump(isset(optional($obj)->id));
dump(optional($obj)->id);
dump(isset(optional($obj)['id']));
dump(optional($obj)['id']);
可以看到輸出是和我們的預想一致的
true
1
false
null
false
null
true
1
本作品採用《CC 協議》,轉載必須註明作者和本文連結