ModernPHP 性状

ModernPHP系列文章 - 什么是性状?以及PHP的性状在什么时候使用,如何使用?

性状是什么

这是PHP5.4.0引入的概念,既像类又像接口。性状是类的部分实现(即常量,属性和方法),可以混入一个或多个类中。性状有两个作用:表名类可以做什么(像是接口),提供模块化的实现(像是类)。

性状的作用

PHP语言使用的是典型的继承模型。在这个模型中,我们先创建基类,实现基本的功能,然后扩展这个基类,通过继承基类创建更多具体的类。这叫做继承层次结构,很多编程语言都是用这个模式。

但是,如果想让两个无关的PHP类具有类型的行为,应该如何去做?例如,Satellite(卫星类)和Car(汽车类)两个类的作用十分不同,在继承层次结构上没有共同的父类,但是这两个类应该都能使用地理编码技术转换成经纬度,然后在地图上显示。

这时候就可以使用性状来解决。性状可以把模块化的实现方式注入到多个无关的类中,还能促进代码的重用。

性状的创建

定义一个性状:

<?php
trait MyTrait {
    // 实现
}

我们接着使用上面提到的例子来演示如何使用性状。我们希望Satellite和Car类都能提供地理编码功能,而且意识到继承和接口都不是最佳方案。我们选择创建Geocodable(地理编码)性状,返回经纬度,然后在地图中绘制。

<?php
trait Geocodable {
    /* @var string */
    protected $address;
    /* @var \Geocoder\Geocoder */
    protected $geocoder;
    /* @var \Geocoder\Result\Geocoded */
    protected $geocoderResult;
 
    public function setGeocoder(\Geocoder\GeocoderInterface $geocoder) 
    {
        $this->geocoder = $geocoder;
    }
 
    public function setAddress($address)
    {
        $this->address = $address;
    }
 
    public function getLatitude()
    {
        if(!isset($this->geocoderResult)) {
            $this->geocodeAddress();
        }
 
        return $this->geocoderResult->getLatitude();
    }
 
    public function getLongitude()
    {
        if(!isset($this->geocoderResult)) {
            $this->geocodeAddress();
        }
 
        return $this->geocoderResult->getLongitude();
    }
 
    protected function geocodeAddress() 
    {
        $this->geocoderResult = $this->geocoder->geocode($this->address);
         
        return true;
    }
}

这个Geocodable性状定义了三个类属性:一个表示地址;一个是地理编码器对象(\Geocoder\Geocoder类的实例,这个类是来自第三方的组件);一个是地理编码器处理后的结果对象(\Geocoder\Result\Geocoded类的实例)。我们还定义了4个公有方法和一个受保护的方法。setGeocoder()方法用于注入Geocoder对象;setAddress()方法用于设定地址;getLatitude()和getLongitude()用于返回经纬度;geocodeAddress()方法把地址字符串传给Geocoder实例,获取地理编码器处理的结果。

性状的使用

<?php
class MyClass {
    use MyTrait;
    // 类的实现
}

这里需要注意,PHP解释器在编译的时候会把性状复制黏贴到类的定义体中,但是不会处理这个操作引入的不兼容问题。如果性状假定类中有特定的方法或属性(在性状中没有定义),要确保相应的类中有对应的属性和方法。

ModernPHP 系列全集:传送门