PHP预定义接口知识梳理
开发PHP多年对预定义接口方面多多少少接触过,至今没有个系统的认识。趁着有时间将这块知识梳理下,方便记忆。
(所有梳理内容建立在PHP7版本之上,更低版本差异自动忽略)
官方手册已经将预定义接口分类明确:
- Traversable 遍历接口
- Iterator 迭代器接口
- IteratorAggregate 聚合式迭代器接口
- ArrayAccess 数组式访问接口
- Serializable 序列化接口
- Closure 闭包类
- Generator 生成器类
Traversable:遍历接口
该接口非常特殊,不能被继承或实现。唯一的用处就是通过使用 instanceof 判断一个类是否实现了该接口功能。所有实现该接口功能的类都将能被遍历操作(foreach)。而且该类被 Iterator 与 IteratorAggregate 所继承。
Iterator:迭代器接口
// 先解读内部方法
Iterator extends Traversable {
// 返回当前游标指向的元素
abstract public current ( void ) : mixed
// 返回当前游标指向的元素对应的键名
abstract public key ( void ) : scalar
// 将游标指向下一个元素
abstract public next ( void ) : void
// 重置游标,将其指向第一个元素
abstract public rewind ( void ) : void
// 判断当前游标指向的是否是元素
abstract public valid ( void ) : bool
}
因为该接口继承自 Traversable 所有实现该接口的类可以被 foreach 遍历。当使用 foreach 遍历对应的类时,以上类方法将在不同时机被调用:
- 执行遍历前,rewind方法将被先执行,说明每次遍历都将先重置游标。
- 遍历开始,valid方法将被执行,判断当前游标元素是否存在,存在继续,不存在则停止遍历。
- 读取当前元素与键名,current与key函数被执行。
- 最后将游标指向(next执行)下一个元素,进入下一轮遍历(从步骤2开始)。
class MyIterator implements Iterator {
private $key = 0;
private $data = [];
public function __construct(Array $data) {
echo '<pre>初始化<br>';
$this->data = $data;
}
public function rewind() {
var_dump('重置游标');
$this->key = 0;
}
public function current() {
var_dump('得到当前元素的值');
return $this->data[$this->key];
}
public function key() {
var_dump('得到当前元素的键名');
return $this->key;
}
public function next() {
var_dump('将游标指向下一个元素');
++$this->key;
}
public function valid() {
var_dump('元素是否有效');
return isset($this->data[$this->key]);
}
}
$data = [
"Element001",
"Element002",
"Element003",
];
$mi = new MyIterator($data);
// foreach相当于上面四步的封装
foreach($mi as $k => $v) {
echo '当前值:'.$v;
echo "<br>-----------------------------<br>";
}
IteratorAggregate:聚合式迭代器接口
该接口与Iterator接口一样继承自Traversable接口。它主要是用来将Iterator接口要求实现的5个迭代器方法委托给其他类的。它可以让你在类的外部实现迭代功能,并允许重新使用常用的迭代器方法,而不是在编写的每个可迭代类中重复这些方法。
// 内部继承
IteratorAggregate extends Traversable {
// 返回一个继承自Traversable的实例类(因其无法继承,所有一般是实现Iterator的)
abstract public getIterator ( void ) : Traversable
}
实例化IteratorAggregate与实例化Iterator一样,都是生成一个迭代器类。不同的是它不需要像Iterator一样再去实现5个方法,只需要实现getIterator方法,并返回一个已经实现Iterator接口的类的实例即可。
要实现这个接口,必须先实现Iterator接口,是不是感觉有点多余?别急看代码:
// 实现聚合迭代器
class MyIteratorAggregate implements IteratorAggregate {
// 数据
protected $data = ['one','two','three'];
// 必须实现的方法
public function getIterator(){
// 返回一个实现Iterator接口类的实例
// 偷懒用上面的那个类
return new MyIterator($this->data);
}
}
其实IteratorAggregate接口就是为简化类似Iterator接口而来。当你需要多个迭代器类,都去实现Iterator接口时,就要每次都实现5个方法,是不是代码很冗余?
有了IteratorAggregate接口,只需要实现一次Iterator接口,以后所有的迭代器实现都交给IteratorAggregate接口就好,如此代码简洁多了。
其实PHP的SPL扩展早就为我们做好了相关简化。它有个ArrayIterator类,就是继承自Iterator接口。
它仿佛就是专门为了给实现IteratorAggregate接口的getIterator方法而存在的。也就是说我们实现getIterator方法其实不需要直接写一个实现Iterator接口的类,直接使用SPL内置的ArrayIterator就可以了。
// 如此完美搞定
public function getIterator(){
// 将上例中 MyIterator 替换成 ArrayIterator 即可
return new ArrayIterator($this->arr);
}
ArrayAccess:数组式访问接口
实现对象像数组一样的访问方式去访问对象的属性。
// 在框架中大量使用,简单粗暴好用!
ArrayAccess {
// 检测偏移位置的值是否存在
abstract public offsetExists ( mixed $offset ) : boolean
// 获取偏移位置的值
abstract public offsetGet ( mixed $offset ) : mixed
// 设置偏移位置的值
abstract public offsetSet ( mixed $offset , mixed $value ) : void
// 复位(删除)偏移位置的值
abstract public offsetUnset ( mixed $offset ) : void
}
// 实例化一个对象
class MyObject implements ArrayAccess {
private $container = [];
public function offsetSet($offset, $value) {
echo '设置偏移位置的值<br>';
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
echo '检测偏移位置的值是否存在<br>';
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
echo '删除偏移位置的值<br>';
unset($this->container[$offset]);
}
public function offsetGet($offset) {
echo '获取偏移位置的值<br>';
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
$cls = new MyObject();
// 自动调用offsetSet方法
$cls['name'] = 'TOOCF';
// 自动调用offsetGet方法
$cls['name'];
// 自动调用offsetExists方法
isset($cls["age"]);
// 自动调用offsetUnset方法
unset($cls["name"]);
Serializable:序列化接口
该接口官方说的比较详细:实现此接口的类将不再支持 __sleep() 和 __wakeup()。不论何时,只要有实例需要被序列化,serialize 方法都将被调用。它将不会调用 __destruct() 或有其他影响,除非程序化地调用此方法。当数据被反序列化时,类将被感知并且调用合适的 unserialize() 方法而不是调用 __construct()。
Serializable {
// 相当于魔术方法 __sleep
abstract public serialize ( void ) : string
// 相当于魔术方法 __wakeup
abstract public unserialize ( string $serialized ) : mixed
}
目前查询很多资料并未找到与sleep与wakeup的大区别,只在序列化后的字符上稍微有一点区别,貌似该区别也没啥用。
Closure:闭包类
// 所有的闭包函数返回的都是一个闭包实例
Closure {
// 用于禁止实例化的构造函数
__construct ( void )
// 将闭包对象绑定到指定的$this对象和类作用域上并返回新闭包类
public static bind ( Closure $closure , object $newthis [, mixed $newscope = 'static' ] ) : Closure
// 同上。不同点是一个静态调用,一个方法调用方式。
public bindTo ( object $newthis [, mixed $newscope = 'static' ] ) : Closure
}
bind与bindTo作用一样,不同的是调用方式。
bind为静态调用 Closure::bind(所要操作的闭包, 闭包指向的类实例, 闭包的作用域)
bindTo的调用方式为 所要操作的闭包->bindTo() 省去bind的第一个参数
此处比较重要的一点就是作用域$newscope,控制着这个匿名函数的访问范围。
默认的访问范围是全局,当传入一个类时,则将访问范围控制在此类的访问范围上。
这里要结合类属性的可访问权限去理解:
public: 共有类型。可在子类中访问和实例化后随意调用
protected: 受保护类型。可在子类中访问,但不能再实例化后调用
private: 私有类型。只能在自己类中使用,子类和不可访问,实例化后也不能调用
回到上面作用域$newscope,默认访问范围是全局时,相当于在实例化后访问,所有只能访问类的public类型。
当指定一个类时,则相当于在类内部访问,则三种权限类型都可访问。
// 定义一个类
class Test {
// 定义一个私有属性
private $name = 'toocf';
private static $age = 18;
}
// 将闭包函数赋予一个变量(变量赋值结束的分号一定不要忘记!!)
$getName = function () {
return $this->name;
};
// 该变量就是一个闭包类实例
var_dump($getName);
// 此时执行闭包函数将报错
// 因为根据上下文找不到$this指向
$getName();
// 给闭包中的$this增加指向,返回新闭包
$getName = $getName->bindTo(new Test);
// 此时$this指向Test实例
// 但是因为在绑定时,没有指定作用域,所有只能访问该类public作用域下的属性
// 因name是私有属性,所有报错
var_dump($getName());
// 增加作用域为该类内部
// 参二可以是Test的实例或者字符串'Test'(注意要写全命名空间,所有写Test::class自动写完整)
$getName = $getName->bindTo(new Test, Test::class);
// 得到 "toocf"
var_dump($getName());
// 访问静态变量
$getAge = function () {
return self::$age;
};
// 访问静态变量唯一不同就是指向的实例化类可以省略
$getAge1 = $getAge->bindTo(null, Test::class);
// 完毕
var_dump($getAge1());
Generator:生成器类
呃,本篇内容有点过长。这样吧,咱们下回再说!