为什么一个对象可以被强制转换为数组,但在 PHP 中不能作为数组参数传递?

Why can an object be cast to an array, but cannot be passed as an array argument in PHP?

提问人:Mark G 提问时间:2/25/2023 更新时间:2/26/2023 访问量:260

问:

如果我有一个在ArrayObject中表示的数组,如下所示:

$arrObj = new ArrayObject([
   'foo' => 1,
   'bar' => 2,
]);

然后我可以舒适地投射它:

print_r((array)$array);

// output is as expected

但是我不能将其作为类型化为数组的参数传递:

function castTest(array $array)
{
}

castTest($arrObj);

// TypeError: castTest(): Argument #1 ($array) must be of type array, ArrayObject given

这是故意的吗?合意?PHP 中是否有任何例外情况可以自动将对象转换为数组?

它适用于字符串对象,例如,实现__toString:

class Str
{
   private string $string;
   public function __construct(string $string)
   {
      $this->string = $string;
   }

   public function __toString()
   {
      return $this->string;
   }
}

function castTest(string $string)
{
   echo $string;
}

$str = new Str('hello, world');
castTest($str); // outputs: hello, world
PHP 数组 参数 转换

评论

0赞 Jared 2/25/2023
我相信这是故意的。任何对象都可以转换为数组,这可能会泄露私有/受保护的成员。因此,自动投射可能会(并最终)带来不必要的结果。如果您认为自己知道自己在做什么,您仍然可以手动投射对象。与字符串自动转换相比 - 最后一个不是完全自动的,你仍然需要实现 ,所以 PHP 相信目标函数会得到正确的数据。由于没有魔法(也没有,等),PHP不能确定自动转换会使一切正确。所以它是被禁止的。__toString__toArray__toInt__toCallable
0赞 nice_dev 2/25/2023
castTest((array)$arrObj);应该工作得很好。当你这样做时,它不会改变。为此,您需要再次将其分配为(array)$arrObj$arrObj$arrObj = (array)$arrObj
0赞 KIKO Software 2/25/2023
看看 ArrayAccess 接口,它允许您以数组形式访问对象。
0赞 Chris Haas 2/25/2023
另一个重要的区别是铸造和型式检查之间的区别。铸造是通过转换原始价值来创造新价值的过程。类型检查只是确保某些内容与类型标准匹配。PHP通过魔术方法满足了这一要求(并被添加为不那么“神奇”)。由于强制转换具有潜在的破坏性,因此调用方在想要匹配类型签名时需要执行。还有类型杂耍,这是它自己的东西string__toStringStringable

答:

0赞 noah1400 2/26/2023 #1

是的,这是故意的。ArrayObject 的行为可能类似于数组,但实际上并不是数组。PHP 在函数参数中强制执行严格的类型。如果参数定义为数组,则它只接受数组。你观察到的方法的行为是不同的,因为当一个对象在字符串上下文中使用时,如果定义了该方法,PHP 会自动调用该方法。但是,此行为不适用于数组或其他非字符串类型。__toString__toString

您可以使用以下方法将 ArrayObject 转换为 ArraygetArrayCopy()

castTest($arrObj->getArrayCopy());
0赞 Jim 2/26/2023 #2

这是因为它是一种不同的类型,并且参数类型检查很严格。数组是一种类型。is 和 “object” 类型(虽然没有类型,但它仍然是 PHP 中一个单独的变量类型)。这也是因为 PHP 没有标量对象。它们是在后台表示标量值的对象。例如,如果创建一个变量,则具有标量对象的语言将透明地实例化对象。过去曾多次向 PHP 提出过这样的建议,但不太可能很快添加。arrayArrayObjectObjectstringString

但是,可以通过更改参数的类型限制来允许数组对象与数组一起传递。PHP 8 引入了使用竖线字符的联合类型。您可以通过添加 ArrayAccess 接口来允许能够像数组一样访问对象:

$arrObj = new ArrayObject([
   'foo' => 1,
   'bar' => 2,
]);

function castTest(array|ArrayAccess $array)
{
}

castTest($arrObj); // No problem here

类型限制意味着“只允许类型或对象类型的变量”。array|ArrayAccessarrayArrayAccess

如果您使用的是旧版本的 PHP(7 或更早版本),您的两个选项是将对象转换为数组(请记住,它不再是函数中的对象,而完全是一个单独的数组,这可能会产生影响,具体取决于数据的处理方式),或者检查函数本身中的类型。以下是一些示例:

  1. 将对象转换为数组(例如,将对象的值复制到新数组中):
function castTest(array $array)
{
}

castTest((array) $arrObj); // No problem here... ?
castTest($arrObj->getArrayCopy()); // Same as above, just using a method instead of type casting.
  1. 检查函数中的类型:
function castTest($array)
{
    if(!is_array($array) && !($array instanceof ArrayAccess)) {
        throw new InvalidArgumentException(
            'Array or ArrayAccess object type expected, ' .
            (is_object($array) ?
                ('object of type ' . get_class($array)) :
                get_type($array)) . ' provided.'
        );
    }
}

castTest($arrObj); // If something other than an array or object with `ArrayAccess` is passed to the function, an exception is thrown.