美文网首页
详解 PHP 7.4 的类型属性

详解 PHP 7.4 的类型属性

作者: PHP9年架构师 | 来源:发表于2020-08-20 14:43 被阅读0次

    PHP 7.4 中增加了类型化类属性,对 php 的类型系统进行了重大改进。这些更改完全是自愿加入的,不会破坏以前的版本。

    在本文中,我们将深入了解该功能,但首先让我们总结一下最重要的几点:

    700粉丝福利安排一波,工作中准备的,大家随便拿

    【社群福利】30G-PHP进阶资料,助力大家都能30K

    这些更改自 PHP 7.4 起可用,计划于 2019 年 11 月发布

    • 它们仅在类中可用,并且需要访问修饰符:public、protected 或 private;或 var

    • 允许所有类型,但 void 和 callable 除外

    • 他们的实际情况是这样的:

    class Foo
    {
        public int $a;
    
        public ?string $b = 'foo';
    
        private Foo $prop;
    
        protected static string $static = 'default';
    }
    

    未初始化

    在进入正题之前,首先要探讨一个与类型属性有关的重要方面。

    不管你第一眼看到这段代码是怎么想的,但它的确是合法的

    class Foo
    {
        public int $bar;
    }
    
    $foo = new Foo;
    

    即便是类的实例化后bar值仍不是整数值的情况下,PHP 也只是会在访问bar时才会报错:

    var_dump($foo->bar);
    
    Fatal error: Uncaught Error: Typed property Foo::$bar 
    must not be accessed before initialization
    

    从错误消息中可以看出,出现了一种新的变量状态:未初始化 (uninitialized)

    $bar 属性无论是否声明了类型,值都可以为 null。因此,无法确定类型属性是否设置。这就是增加变量「未初始化」状态的原因。

    未初始化有四个方面需要注意:

    • 无法读取未初始化的属性,一旦这么做,将引发致命错误;

    • 由于在访问属性时会检查未初始化状态,所以即使是不可为空的对象也可以使用未初始化属性;

    • 在读取未初始化属性时候之前可以对其进行写入;

    • unset 操作会让类型属性变成未初始化状态,而非类型属性只会变成值为 null;

    特别要注意在对象实例化之后设置未初始化的类型属性是合法的:

    class Foo
    {
        public int $a;
    }
    
    $foo = new Foo;
    
    $foo->a = 1; // 合法
    $foo->a = null; // 非法
    

    虽然只会在读取属性值时检查未初始化状态,但在写入属性时会进行类型验证。这意味着任何无效的属性值都不会被设置。

    默认值和构造函数

    让我们仔细看看如何初始化类型属性值。对于标量类型,可以直接提供一个默认值

    class Foo
    {
        public int $bar = 4;
    
        public ?string $baz = null;
    
         // 错误写法 public string $baz = null;
    
        public array $list = [1, 2, 3];
    }
    

    类型属性不能显示设置为null,除非是可空类型。这看上去显而易见的,但是一些旧行为却允许这种操作

    function passNull(int $i = null)
    { /* … */ }
    
    passNull(null);
    

    幸运的是,类型属性不允许这种令人疑惑的行为。 还要注意,属性类型的默认值不可能为对象或者类,你应当使用构造器来设置这些值。

    最明显的用来设置默认值的地方就是构造函数

    class Foo
    {
        private int $a;
    
        public function __construct(int $a)
        {
            $this->a = $a;
        }
    }
    

    但也要记住我之前提到的内容:在构造函数之外写入未初始化 (uninitialized) 的属性是有效的。只要没有读取属性值的操作,编译器就不会执行未初始化的相关检查。

    类型

    那么到底哪些类型可以指定,又如何指定呢?我已经提到了指定属性类型只能在类中进行 (当前如此),并且它们需要一个访问修饰符或是属性前面的 var 关键字。

    对于可用类型,几乎所有类型都可以使用,除了 void 和 callable 类型.

    因为 void 意味着没有值,所以它不能用于指定一个值的类型也就说得过去了。然而 callback 就有一点细微不同了。

    可见,PHP 中的 "callback" 可以这样写

    $callable = [$this, 'method'];
    

    假设你有以下 (无效) 代码:

    class Foo
    {
        public callable $callable;
    
        public function __construct(callable $callable)
        { /* … */ }
    }
    
    class Bar
    {
        public Foo $foo;
    
        public function __construct()
        {
            $this->foo = new Foo([$this, 'method'])
        }
    
        private function method()
        { /* … */ }
    }
    
    $bar = new Bar;
    
    ($bar->foo->callable)();
    

    在此例中,$callback 引用了私有的 Bar::method,但是是在 Foo 的上下文中被调用的。基于这个问题,决定不添加 callback 类型的支持。

    不过这并不是什么大问题,因为 Closure(闭包) 是一种有效类型,它会记住构建它的 $this 上下文。

    顺带一说,以下是所有可用类型的列表:

    
    *   bool
      
    *   int
      
    *   float
      
    *   string
      
    *   array
      
    *   iterable
      
    *   object
      
    *   ? (nullable)
      
    *   self & parent
      
    *   Classes & interfaces
      ~~~
          
    
    ## 强制和严格类型
    
    PHP,是我们既喜欢又反感的动态语言,它会尽可能地强制或转换类型。假设你在一个期望接受 int 的地方传入字符串,PHP 会试着自动转换该字符串:
    
    
    
    

    function coerce(int $i)
    { /* … */ }

    coerce('1'); // 1

    
    同样的原则也适用于已指定类型的属性,下面的代码是有效的,且会将'1'转换为1.
    
    

    class Bar
    {
    public int $i;
    }

    $bar = new Bar;

    $bar->i = '1'; // 1

    
    如果你并不喜欢这种 (自动转换) 行为,可以通过声明严格类型来禁用它:
    
    

    declare(strict_types=1);

    $bar = new Bar;

    $bar->i = '1'; // 1

    Fatal error: Uncaught TypeError:
    Typed property Bar::$i must be int, string used

    
    ## 类型差异和继承
    
    即使 PHP 7.4 引入了 [改进的类型差异](https://link.zhihu.com/?target=https%3A//stitcher.io/blog/new-in-php-74%23improved-type-variance-rfc) , 但是类型的属性仍然是不变的。这意味着以下写法是无效的:
    
    

    class A {}
    class B extends A {}

    class Foo
    {
    public A $prop;
    }

    class Bar extends Foo
    {
    public B $prop;
    }

    Fatal error: Type of Bar::$prop must be A (as in class Foo)

    
    如果上面的示例看起来不够明显的话,你可以查看以下内容:
    
    

    class Foo
    {
    public self $prop;
    }

    class Bar extends Foo
    {
    public self $prop;
    }

    
    在运行代码之前,PHP 将在背后用它所引用的具体实现类来替换self。这意味着在此本例中将抛出相同的错误。解决此问题的唯一方法是执行以下操作:
    
    

    class Foo
    {
    public Foo $prop;
    }

    class Bar extends Foo
    {
    public Foo $prop;
    }

    
    谈到继承,您可能会发现很难想出任何好的用例来重写继承属性的类型。
    
      
    
    尽管我同意这种观点,但值得注意的是更改继承属性的类型是可能实现的,前提是访问修饰符也必须从 private 更改为 protected 或 public。
    
      
    
    以下代码是有效的:
    
      
    
    

    class Foo
    {
    private int $prop;
    }

    class Bar extends Foo
    {
    public string $prop;
    }

    
    但是,从可空的类型改为不可空或反向的类型是不允许的。
    
    

    class Foo
    {
    public int a; public ?intb;
    }

    class Bar extends Foo
    {
    public ?int a; public intb;
    }

    Fatal error: Type of Bar::$a must be int (as in class Foo)

    
    还有更多!
    
    正如开头所说,类型化属性是 PHP 的 主要 补充。关于它们更多的内容,我建议您通读 RFC 以了解所有细节。
    
      
    
    如果您不熟悉 PHP 7.4,则可能需要阅读 完整列表 中所做的更改和添加的功能。老实说,这是很长一段时间以来最好的发行版之一,值得您花时间!
    
      
    
    ## 大厂必备面试题
    
    [2020最新大厂PHP面试题(附答案)](https://zhuanlan.zhihu.com/p/188527705)
    
      
    
    以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的可以加入我的官方群[点击此处](https://jq.qq.com/?_wv=1027&k=JNZRtm7R)。
    喜欢我的文章就点赞关注吧

    相关文章

      网友评论

          本文标题:详解 PHP 7.4 的类型属性

          本文链接:https://www.haomeiwen.com/subject/wvgsjktx.html