PHP 7中属性的类型提示?

98

PHP 7是否支持对类属性进行类型提示?

我的意思是,不仅仅是对于 setters/getters(设置器/获取器),而是针对属性本身。

类似于:

class Foo {
    /**
     *
     * @var Bar
     */
    public $bar : Bar;
}

$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error

1
据我所知,没有这样的功能。然而,一般来说,对于属性值的任何限制都应该通过 setter 方法来实现。由于 setter 方法可以轻松地为“value”参数设置类型提示,因此您可以放心使用。 - Niet the Dark Absol
许多框架都使用 protected 属性(主要用于控制器)。对于这些特定情况,它非常有用。 - CarlosCarucce
4个回答

159

PHP 7.4 将支持 类型属性,例如:

class Person
{
    public string $name;
    public DateTimeImmutable $dateOfBirth;
}

PHP 7.3及更早版本不支持此功能,但有一些替代方案。

您可以创建一个私有属性,只能通过具有类型声明的getter和setter访问:


class Person
{
    private $name;
    public function getName(): string {
        return $this->name;
    }
    public function setName(string $newName) {
        $this->name = $newName;
    }
}

您还可以创建公共属性并使用文档块为阅读代码和使用IDE的人提供类型信息,但这不提供运行时类型检查:

class Person
{
    /**
      * @var string
      */
    public $name;
}

实际上,您可以结合使用getter和setter以及文档块。

如果您更加冒险,您可以使用__get__set__isset__unset魔术方法创建一个虚拟属性,并自行检查类型。虽然我不确定我是否推荐这样做。


听起来非常不错。迫不及待地想看看下一次发布会有什么新动态! - CarlosCarucce
另一个重要问题是引用的处理,它们与类型声明并不真正交互良好,可能必须为此类属性禁用。即使没有性能问题,无法执行例如 array_push($this->foo, $bar)sort($this->foobar) 这样的操作也将是一个大问题。 - Andrea
类型强制转换会如何工作?例如:(new Person())->dateOfBirth = '2001-01-01';... 假设提供了 declare(strict_types=0);。它会抛出异常还是使用 DateTimeImmutable 构造函数?如果是后者,那么如果字符串无效日期,将抛出哪种类型的错误?TypeError - lmerino
@Imerino 从来没有隐式转换为DateTime(Immutable)。 - Andrea

13

7.4+:

好消息是,正如@Andrea指出的那样,它将在新版本中实现。 我会把这个解决方案留在这里,以防有人想在7.4之前使用它。


7.3或更低版本

根据我从这个主题仍然收到的通知,我相信许多人都遇到了和我一样的问题。我针对这种情况的解决方案是,在一个 trait 中组合 setters + __set 魔法方法,以模拟这种行为。下面是代码:

trait SettersTrait
{
    /**
     * @param $name
     * @param $value
     */
    public function __set($name, $value)
    {
        $setter = 'set'.$name;
        if (method_exists($this, $setter)) {
            $this->$setter($value);
        } else {
            $this->$name = $value;
        }
    }
}

这里是演示:

class Bar {}
class NotBar {}

class Foo
{
    use SettersTrait; //It could be implemented within this class but I used it as a trait for more flexibility

    /**
     *
     * @var Bar
     */
    private $bar;

    /**
     * @param Bar $bar
     */
    protected function setBar(Bar $bar)
    {
        //(optional) Protected so it wont be called directly by external 'entities'
        $this->bar = $bar;
    }
}

$foo = new Foo();
$foo->bar = new NotBar(); //Error
//$foo->bar = new Bar(); //Success

解释

首先,将bar定义为私有属性,这样PHP就会自动地转换__set

__set将检查当前对象中是否声明了某个setter(method_exists($this,$setter))。否则,它将像通常一样只设置其值。

声明一个setter方法(setBar),该方法接收带类型提示的参数(setBar(Bar $bar))。

只要PHP检测到传递给setter的不是Bar实例,它就会自动触发致命错误:Uncaught TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of NotBar given


10

PHP 7.4的编辑:

自从PHP 7.4以来,您可以使用属性类型(文档 / 维基),这意味着您可以执行以下操作:

    class Foo
{
    protected ?Bar $bar;
    public int $id;
    ...
}

根据维基百科,所有可接受的值为:
  • bool,int,float,string,array,object
  • iterable
  • self,parent
  • 任何类或接口名称
  • ?type //其中"type"可以是上述任何一种类型
PHP <7.4 实际上这是不可能的,你只有4种方法来模拟它:
  • 默认值
  • 注释块中的装饰器
  • 构造函数中的默认值
  • Getter和Setter
我在这里将它们结合起来了。
class Foo
{
    /**
     * @var Bar
     */
    protected $bar = null;

    /** 
    * Foo constructor
    * @param Bar $bar
    **/
    public function __construct(Bar $bar = null){
        $this->bar = $bar;
    }
    
    /**
    * @return Bar
    */
    public function getBar() : ?Bar{
        return $this->bar;
    }

    /**
    * @param Bar $bar
    */
    public function setBar(Bar $bar) {
        $this->bar = $bar;
    }
}

请注意,自PHP 7.1(可空)起,您实际上可以将返回类型定义为?Bar,因为它可能为空(在PHP 7.0中不可用)。
您还可以将返回类型定义为void,自PHP 7.1起。

1
你可以使用setter。
class Bar {
    public $val;
}

class Foo {
    /**
     *
     * @var Bar
     */
    private $bar;

    /**
     * @return Bar
     */
    public function getBar()
    {
        return $this->bar;
    }

    /**
     * @param Bar $bar
     */
    public function setBar(Bar $bar)
    {
        $this->bar = $bar;
    }

}

$fooInstance = new Foo();
// $fooInstance->bar = new NotBar(); //Error
$fooInstance->setBar($fooInstance);

输出:

TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of Foo given, called in ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接