键入提示 - 指定对象数组

Yoa*_*osh 20 php arrays type-hinting

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

class Foo {}
Run Code Online (Sandbox Code Playgroud)

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

function getFoo(Foo $f) {}
Run Code Online (Sandbox Code Playgroud)

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

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

有没有办法克服这个问题?也许是这样的

function getFoo(Foo $f[]) {}
Run Code Online (Sandbox Code Playgroud)

bis*_*hop 26

如果您想确保使用"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');
    }
}
Run Code Online (Sandbox Code Playgroud)

然后:

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

$foos = new ArrayOfFoos();
$foos[] = new Foo();
workWithFoo($foos);
Run Code Online (Sandbox Code Playgroud)

秘诀就是你要定义一个新的"foo数组"类型,然后使用类型提示保护传递"类型".


如果您不想自己滚动,Haldayne库会处理样板以进行成员资格要求检查:

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

(完全披露,我是Haldayne的作者.)


历史记录:RFC阵列在2014年提出了这个功能.RFC在4 yay和16 nay时被拒绝了.这个概念最近重新出现在内部列表中,但投诉与对原始RFC的征收大致相同:添加此检查会显着影响性能.


小智 13

可以使用旧的帖子但是可变参数函数和数组解包(有一些限制)来完成类型化数组提示,至少使用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"
Run Code Online (Sandbox Code Playgroud)


限制:

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

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

    test($arrayOfFoo)
    
    Run Code Online (Sandbox Code Playgroud)

    在上面的示例的上下文中将产生类型错误,因为编译器需要foo的参数,而不是数组.请参阅下面的一个虽然是hacky,直接传递给定类型数组的解决方案,同时保留一些类型提示.

  3. 变量函数可能只有一个可变参数,它必须是最后一个参数(因为否则编译器如何确定变量参数的结束位置和下一个参数的开始)意味着你不能沿着

    function test(Foo ...$foos, Bar ...$bars){ 
        //...
    }
    
    Run Code Online (Sandbox Code Playgroud)

    要么

    function test(Foo ...$foos, Bar $bar){
        //...
    }
    
    Run Code Online (Sandbox Code Playgroud)


一种仅比稍微好一点的检查 - 每元素替代方案:

下面的过程不是仅仅检查每个元素的类型更好,只要(1)它保证在功能体所使用的参数是正确的类型而不会扰乱与类型检查的功能,和(2)它抛出的通常类型的例外.

考虑:

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

        //treat as regular function body

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

    })(...$foos);
}
Run Code Online (Sandbox Code Playgroud)

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

alt($arrayOfFoo) // also outputs "foobar"
Run Code Online (Sandbox Code Playgroud)

这种方法的问题包括:

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

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

(3)它就像内部检查数组元素一样,将类型检查视为实现细节,只要必须检查函数声明(或享受类型异常)以实现只有特定类型的数组才是有效参数.在接口或抽象函数中,无法对完整类型提示进行编码; 所有人都可以做的是评论预期上述类型(或类似的东西)的实现.


笔记

[1].简而言之:数组解包呈现等价物

example_function($a,$b,$c);
Run Code Online (Sandbox Code Playgroud)

example_function(...[$a,$b,$c]);
Run Code Online (Sandbox Code Playgroud)


[2].简而言之:表单的可变函数

function example_function(Foo ...$bar){
    //...
}
Run Code Online (Sandbox Code Playgroud)

可以通过以下任何方式有效地调用:

example_function();
example_function(new Foo());
example_function(new Foo(), new Foo());
example_function(new Foo(), new Foo(), new Foo());
//and so on
Run Code Online (Sandbox Code Playgroud)


Rol*_*olf 7

抱歉,PHP无法正常工作.它已经取得了quick'n'easy编程,所以你不严格打字,这让你在动态类型地狱没有任何帮助(如类型推断编译器)困扰.PHP解释器对你放入数组中的内容完全不了解,因此如果想要验证类似的内容,它必须遍历所有数组条目

function bar(Foo[] $myFoos)
Run Code Online (Sandbox Code Playgroud)

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

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

所以,只是在函数签名中进行PHP类型验证,我会避免使用强类型数组包装器.我不相信给你足够的投资回报率.PHPDoc支持良好的PHP IDE可以为您提供更多帮助,而在PHPDoc标签中,Foo []符号可以使用.

另一方面,如果你可以将一些好的业务逻辑集中在它们中,那么包装器就有意义了.

当使用强类型数组(或更精确:强类型字典)扩展PHP时,也许有一天会到来.我想要那个.有了这些,他们可以提供不会惩罚你的签名提示.

  • 好吧,好吧。我为此添加了一个注释。 (2认同)