键入提示 - 指定对象数组

时间:2013-12-24 16:10:42

标签: php arrays type-hinting

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

class Foo {}

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

function getFoo(Foo $f) {}

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

  

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

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

function getFoo(Foo $f[]) {}

5 个答案:

答案 0 :(得分:24)

如果您想确保使用“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 yay和16 nay而被拒绝。最近的概念是reappeared on the internals list,但投诉与原始RFC的投诉大致相同:添加此检查会significantly effect performance

答案 1 :(得分: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"


限制:

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

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

    test($arrayOfFoo)
    
    在上面示例的上下文中,

    将产生类型错误,因为编译器需要foo的参数,而不是数组。请参阅下面的一个尽管是hacky的解决方案,直接传递给定类型的数组,同时保留某些类型提示。

  3. 可变参数函数可能只有一个可变参数,它必须是最后一个参数(因为否则编译器如何确定可变参数的结束位置和下一个参数的开始位置)意味着您无法按照以下方式声明函数:

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

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

  4. 只比每个元素更好的检查 -

    以下过程 比仅检查每个元素的类型更好,因为(1)它保证功能体中使用的参数具有正确的类型,而不会使类型检查使函数混乱, (2)它抛出了通常的类型例外。

    考虑一下:

    function alt(Array $foos){
        return (function(Foo ...$fooParams){
    
            //treat as regular function body
    
            foreach($fooParams as $foo){
                $foo->test();
            }
    
        })(...$foos);
    }
    

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

    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
    

答案 2 :(得分:7)

抱歉,PHP无法正常工作。它已被用于快速编程,因此您不会受到严格打字的困扰,这会使您处于动态类型地狱而没有任何帮助(如类型推理编译器)。 PHP解释器完全不知道你已经放入数组中的内容,所以它必须遍历所有数组条目,如果它想要验证像

这样的东西
function bar(Foo[] $myFoos)

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

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

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

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

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

答案 3 :(得分:3)

function getFoo()

通常,您会有一个可以键入Foo

的添加方法
function addFoo( Foo $f )

因此,getter会返回一个Foo数组,add方法可以确保你只有Foo到数组。

编辑从getter中删除了参数。我不知道我在想什么,你不需要在吸气器中争论。

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

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的数组,但它有助于说明课堂上发生了什么。

答案 4 :(得分:2)

class Foo {
/**
 * @param Foo[] $f
 */
    function getFoo($f) {

    }
}