【问题标题】:Type hinting - specify an array of objects类型提示 - 指定对象数组
【发布时间】:2014-01-12 20:45:02
【问题描述】:

如何将参数类型指定为数组? 假设我有一个名为“Foo”的类:

class Foo {}

然后我有一个接受该类类型作为参数的函数:

function getFoo(Foo $f) {}

当我传入一个 'Foo's 数组时,我得到一个错误,说:

可捕获的致命错误:参数 1 传递给 getFoo() 必须是 Foo 的实例,给定数组

有没有办法解决这个问题?也许像

function getFoo(Foo $f[]) {}

【问题讨论】:

  • 要么是数组,要么是对象。 PHP 不关心数组中的内容。您不能指定“我希望这个函数接受数组,但我希望这个数组只有 Foo 类型的对象”
  • 嗯...@RoyalBg 你确定吗?太可惜了……
  • @RoyalBg 是对的。我为此挠头。如果您要传递一个对象数组,您只需为该函数设置function getFoo($f) { ... }。所以如果你想要一个专门处理数组的函数,只需创建一个新函数function getFooArray($f) { ... }
  • 您需要编写一个只接受 Foo 对象的集合类。请参阅@bishop 的回答以了解总体思路。一些 PHP 库有可以参考的通用集合对象,例如 Doctrine 的 Collection 对象。
  • 只是想知道我们对 splat 运算符 function foo(Abc ...$args) { } foo(...$arr); 找到的解决方案的看法 here

标签: php arrays type-hinting


【解决方案1】:

如果您想确保您使用的是“Array of Foo”并且您想确保方法接收“Array of Foo”,您可以:

class ArrayOfFoo extends \ArrayObject {
    public function offsetSet($key, $val) {
        if ($val instanceof Foo) {
            return parent::offsetSet($key, $val);
        }
        throw new \InvalidArgumentException('Value must be a Foo');
    }
}

然后:

function workWithFoo(ArrayOfFoo $foos) {
    foreach ($foos as $foo) {
        // etc.
    }
}

$foos = new ArrayOfFoos();
$foos[] = new Foo();
workWithFoo($foos);

秘诀在于,您要定义“foo 数组”的新“类型”,然后使用类型提示保护传递该“类型”。


Haldayne library 处理成员要求检查的样板,如果您不想自己推出:

class ArrayOfFoo extends \Haldayne\Boost\MapOfObjects {
    protected function allowed($value) { return $value instanceof Foo; }
}

(完全公开,我是 Haldayne 的作者。)


历史记录:Array Of RFC 早在 2014 年就提出了此功能。RFC 以 4 yy 和 16 nay 被拒绝。这个概念最近reappeared on the internals list,但投诉与针对原始RFC 征收的投诉大致相同:添加此检查将significantly affect performance

【讨论】:

  • 很好的解决方案,但是您忘记覆盖构造函数并在那里添加检查。
【解决方案2】:

旧帖子但可变参数函数和数组解包可用于(有一些限制)来完成类型化数组提示,至少在 PHP7 中是这样。 (我没有在早期版本上进行测试)。

示例:

class Foo {
  public function test(){
    echo "foo";
  }   
};  

class Bar extends Foo {
  //override parent method
  public function test(){
    echo "bar";
  }   
}          

function test(Foo ...$params){
  foreach($params as $param){
    $param->test();
  }   
}   

$f = new Foo();
$b = new Bar();

$arrayOfFoo = [$f,$b];

test(...$arrayOfFoo);
//will output "foobar"


限制:

  1. 这在技术上不是一个解决方案,因为您并没有真正传递一个类型化的数组。相反,您使用数组解包运算符1(函数调用中的“...”)将数组转换为参数列表,每个参数都必须是可变参数声明中提示的类型2(也使用省略号)。

  2. 函数调用中的“...”是绝对必要的(鉴于上述情况,这并不奇怪)。试图打电话

    test($arrayOfFoo)
    

    在上面示例的上下文中会产生类型错误,因为编译器需要 foo 的参数,而不是数组。请参阅下面的解决方案,虽然很老套,但可以直接传入给定类型的数组,同时保留 some 类型提示。

  3. 可变参数函数可能只有一个可变参数并且它必须是最后一个参数(否则编译器将如何确定可变参数的结束位置和下一个参数的开始位置)意味着您不能按照

    function test(Foo ...$foos, Bar ...$bars){ 
        //...
    }
    

    function test(Foo ...$foos, Bar $bar){
        //...
    }
    


仅比仅检查每个元素的替代方案略好:

以下过程 比仅检查每个元素的类型要好,因为 (1) 它保证函数体中使用的参数是正确的类型,而不会使函数与类型检查混淆, (2) 它抛出通常的类型异常。

考虑:

function alt(Array $foos){
    return (function(Foo ...$fooParams){

        //treat as regular function body

        foreach($fooParams as $foo){
            $foo->test();
        }

    })(...$foos);
}

这个想法是定义并返回立即调用的闭包的结果,该闭包会为您处理所有可变参数/解包业务。 (可以进一步扩展该原理,定义一个生成该结构的函数的高阶函数,减少样板文件)。在上面的例子中:

alt($arrayOfFoo) // also outputs "foobar"

这种方法的问题包括:

(1) 特别是对于没有经验的开发者,可能不清楚。

(2) 可能会产生一些性能开销。

(3) 就像在内部检查数组元素一样,将类型检查视为实现细节,因为必须检查函数声明(或享受类型异常)才能意识到只有特定类型的数组才是有效参数。在接口或抽象函数中,无法编码完整的类型提示;唯一能做的就是评论上述类型(或类似的)的实现是预期的。


备注

[1]。简而言之:数组解包呈现等效

example_function($a,$b,$c);

example_function(...[$a,$b,$c]);


[2]。简而言之:形式的可变参数函数

function example_function(Foo ...$bar){
    //...
}

可以通过以下任何一种方式进行有效调用:

example_function();
example_function(new Foo());
example_function(new Foo(), new Foo());
example_function(new Foo(), new Foo(), new Foo());
//and so on

【讨论】:

    【解决方案3】:

    抱歉,PHP 不能以这种方式工作。它是为快速编程而设计的,因此您不必为严格的类型而烦恼,这会使您在没有任何帮助的情况下陷入动态类型地狱(如类型推断编译器)。 PHP 解释器完全不知道您放入数组中的内容,因此如果它想要验证类似以下内容(当然,这在 PHP 中不起作用),它必须遍历所有数组条目:

    function bar(Foo[] $myFoos)
    

    当数组变大时,这会对性能产生重大影响。我认为这就是 PHP 不提供类型化数组提示的原因。

    这里的其他答案建议您创建强类型数组包装器。当你有一个像 Java 或 C# 这样的泛型类型的编译器时,包装器很好,但对于 PHP,我不同意。在这里,这些包装器是乏味的样板代码,您需要为每个类型化数组创建一个。如果你想使用库中的数组函数,你需要使用类型检查委托函数扩展你的包装器并膨胀你的代码。这可以在学术样本中完成,但在具有许多类和集合的生产系统中,开发人员的时间成本很高且 Web 集群中的 MIPS 稀缺?我认为不会。

    所以,为了在函数签名中进行 PHP 类型验证,我会避免使用强类型数组包装器。我不相信这会给你足够的投资回报率。良好的 PHP IDE 的 PHPDoc 支持可以为您提供更多帮助,并且在 PHPDoc 标签中,Foo[] 表示法有效。

    另一方面,如果您可以将一些好的业务逻辑集中到包装器中,包装器就会有意义。

    也许有一天会用强类型数组(或更准确地说:强类型字典)扩展 PHP。我愿意。有了这些,他们可以提供不会惩罚你的签名提示。

    【讨论】:

    • 显示的代码有误!读者可能会认为这是一个令人困惑的解决方案。
    • 好吧,好吧。我为此添加了一条注释。
    【解决方案4】:
    function getFoo()
    

    通常,您将拥有一个 add 方法,该方法会键入提示 Foo

    function addFoo( Foo $f )
    

    因此,getter 将返回一个 Foo 数组,而 add 方法可以确保您只有 Foo 到数组中。

    EDIT 从 getter 中删除了参数。我不知道我在想什么,你不需要在 getter 中争论。

    EDIT 只是为了显示一个完整的类示例:

    class FooBar
    {
        /**
         * @return array
         */
        private $foo;
    
        public function getFoo()
        {
            return $foo;
        }
    
        public function setFoo( array $f )
        {
            $this->foo = $f;
    
            return $this;
        }
    
        public function addFoo( Foo $f )
        {
            $this->foo[] = $f;
    
            return $this;
        }
    }
    

    您通常并且可能不应该使用 setter 方法,因为您拥有 add 方法来帮助确保 $fooFoo 的数组,但它有助于说明课堂上发生的事情。

    【讨论】:

    • 一个不错的简单解决方案
    【解决方案5】:
    class Foo {
        /**
         * @param Foo[] $f
         */
        function getFoo($f) {
    
        }
    }
    

    【讨论】:

    • 澄清一下,phpdoc 类型提示@param Foo[] ... 不是 JS 标准。但是,它非常有用; phpstorm 等 IDE 使用它来改进智能感知。
    猜你喜欢
    • 2017-09-09
    • 2010-10-21
    • 2018-01-04
    • 2011-04-29
    • 2010-12-05
    • 1970-01-01
    相关资源
    最近更新 更多