PHP学习笔记20:预定义接口
图源:php.net
Traversable
这是一个代表可迭代类型的基本接口,其本身并没有任何方法:
interface Traversable {
}
所以不能直接通过实现该接口来让类具备迭代功能,该接口仅仅用来判断一个类型是否具有迭代能力:
<?php
$arr = [1, 2, 3];
var_dump($arr instanceof Traversable);
function create_gen(): Generator
{
yield 0;
yield 1;
yield 2;
}
$gen = create_gen();
var_dump($gen instanceof Traversable);
遗憾的是,在php中数组不能像类一样使用instanceof 判别类型,所以尝试通过instanceof Traversable 判断数组得到的结果是false 。而生成器函数返回的生成器是一个Traversable ,原因是生成器Generator 实现了Iterator 接口,而Iterator 接口继承自Traversable 接口。
Iterator
Iterator 接口代表一个迭代器,继承自Traversable 。之前在PHP学习笔记17:迭代器和生成器中已经有过详细说明如何使用此接口实现一个迭代器。
IteratorAggregate
IteratorAggregate (聚合式迭代器)同样继承自Traversable ,它只有一个方法,可以返回一个迭代器:
interface IteratorAggregate extends Traversable {
public getIterator(): Traversable
}
这个接口的作用类似于Python中的Iterable 接口,其作用是可以将迭代器的实现和类本身解耦。
我们看一个示例:
<?php
class Sentence implements Iterator
{
private array $words;
private int $index = 0;
public function __construct(string $string)
{
$this->words = str_word_count($string, 1);
}
public function current(): mixed
{
return $this->words[$this->index];
}
public function rewind(): void
{
$this->index = 0;
}
public function next(): void
{
$this->index++;
}
public function key(): mixed
{
return $this->index;
}
public function valid(): bool
{
return isset($this->words[$this->index]);
}
}
$sentence = new Sentence("hello world, how are you!");
foreach ($sentence as $word) {
echo $word . PHP_EOL;
}
echo PHP_EOL;
这是在PHP学习笔记17:迭代器和生成器中作为示例的一个类Sentence ,它实现了Iterator 接口,可以将字符串以单词的方式进行遍历。
这个类的缺点是因为直接实现了Iterator 接口,类Sentence 和迭代器的实现代码是紧耦合,整个代码显得很臃肿,扩展性也比较差。我们可以利用IteratorAggregage 接口来改善这点:
<?php
class Sentence implements IteratorAggregate
{
private array $words;
public function __construct(string $string)
{
$this->words = str_word_count($string, 1);
}
public function getIterator(): Traversable
{
return new class($this->words) implements Iterator
{
private array $words;
private int $index = 0;
public function __construct(array &$words)
{
$this->words = &$words;
}
public function current(): mixed
{
return $this->words[$this->index];
}
public function rewind(): void
{
$this->index = 0;
}
public function next(): void
{
$this->index++;
}
public function key(): mixed
{
return $this->index;
}
public function valid(): bool
{
return isset($this->words[$this->index]);
}
};
}
}
$sentence = new Sentence("hello world, how are you!");
foreach ($sentence as $word) {
echo $word . PHP_EOL;
}
echo PHP_EOL;
这是用匿名类实现的版本,在Java中对此类情况一般还可以使用“内嵌类”来实现,但遗憾的是php并不支持内嵌类。
Throwable
Throwable 定义了可以通过throw 抛出的类型(Exception 和Error ):
interface Throwable {
public getMessage(): string
public getCode(): int
public getFile(): string
public getLine(): int
public getTrace(): array
public getTraceAsString(): string
public getPrevious(): ?Throwable
abstract public __toString(): string
}
用户代码并不能直接实现Throwable ,而应当从Exception 继承。
ArrayAccess
ArrayAccess 定义了数组访问相关的接口:
interface ArrayAccess {
public offsetExists(mixed $offset): bool
public offsetGet(mixed $offset): mixed
public offsetSet(mixed $offset, mixed $value): void
public offsetUnset(mixed $offset): void
}
实现了该接口的类可以使用下标像数组那样访问:
<?php
class Sentence implements ArrayAccess
{
private array $words;
public function __construct(string $string)
{
$this->words = str_word_count($string, 1);
}
public function get_length(): int
{
return count($this->words);
}
public function offsetExists(mixed $offset): bool
{
return isset($this->words[$offset]);
}
public function offsetGet(mixed $offset): mixed
{
return $this->words[$offset];
}
public function offsetSet(mixed $offset, mixed $value): void
{
$this->words[$offset] = $value;
}
public function offsetUnset(mixed $offset): void
{
unset($this->words[$offset]);
}
}
$sentence = new Sentence("hello world, how are you!");
for ($i = 0; $i < $sentence->get_length(); $i++) {
echo $sentence[$i] . " ";
}
echo PHP_EOL;
遗憾的是实现了ArrayAccess 接口后并不能“自动”实现Traversable 接口,也就是说不能使用foreach 进行遍历。从这方面讲是不如Python灵活的,类似的情况Python会自动满足。
Serializable
Serializable 负责对象的序列化和反序列化:
interface Serializable {
public serialize(): ?string
public unserialize(string $data): void
}
在PHP学习笔记12:类和对象IV中讨论魔术方法的时候已经涉及这个接口,这里不做赘述。
Closure类
Closure (闭包)是一个类而非接口,php用这个类实现了匿名函数。
final class Closure {
private __construct()
public static bind(Closure $closure, ?object $newThis, object|string|null $newScope = "static"): ?Closure
public bindTo(?object $newThis, object|string|null $newScope = "static"): ?Closure
public call(object $newThis, mixed ...$args): mixed
public static fromCallable(callable $callback): Closure
}
关于匿名函数可以阅读PHP学习笔记8:函数中匿名函数的部分。
Generator类
Generator 同样是一个类,代表生成器:
final class Generator implements Iterator {
public current(): mixed
public getReturn(): mixed
public key(): mixed
public next(): void
public rewind(): void
public send(mixed $value): mixed
public throw(Throwable $exception): mixed
public valid(): bool
public __wakeup(): void
}
生成器不能直接使用new 关键字创建,而应当使用生成器函数。更多生成器的内容见PHP学习笔记17:迭代器和生成器。
Fliber类
Fliber 是php8.1.0新加入的协程的核心机制,可以用于创建协程:
final class Fiber {
public start(mixed ...$args): mixed
public resume(mixed $value = null): mixed
public throw(Throwable $exception): mixed
public getReturn(): mixed
public isStarted(): bool
public isSuspended(): bool
public isRunning(): bool
public isTerminated(): bool
public static suspend(mixed $value = null): mixed
public static getCurrent(): ?Fiber
}
Fliber 和协程的更多内容可以阅读PHP学习笔记18:协程。
WeakReference类
具有自动垃圾回收机制的语言通常使用引用计数来判断一个变量是否可以被回收,这意味着只要变量有至少一个引用,它就不能被回收。但某些情况下我们可能不希望出现类似的情况,比如移动开发中,APP的页面UI和后台服务是相对分离的,页面UI随时可以被系统服务暂停或关闭,这种情况下页面的元素当然要被垃圾回收,但如果后台服务对页面中的组件进行了引用,就会阻止正常的垃圾回收。
这时候就需要使用WeakReference (弱引用)。
弱引用和普通引用的区别在于,虽然前者依然建立了到目标变量的引用关系,可以正常获取到目标变量的值,但这种引用关系并不会影响到垃圾回收。
php的WeakReference 类定义为:
final class WeakReference {
public __construct()
public static create(object $object): WeakReference
public get(): ?object
}
来看一个示例:
<?php
$obj = new stdClass;
$weakObj = WeakReference::create($obj);
var_dump($weakObj->get());
unset($obj);
var_dump($weakObj->get());
WeakMap类
WeakMap 可以简单看做是使用WeakReference 作为key的映射。也就是说,使用引用作为WeakMap 的key并不会影响到对应变量的引用计数和垃圾回收。
final class WeakMap implements Countable, ArrayAccess, IteratorAggregate {
public __construct()
public count(): int
public getIterator(): Iterator
public offsetExists(object $object): bool
public offsetGet(object $object): mixed
public offsetSet(object $object, mixed $value): void
public offsetUnset(object $object): void
}
来看一个示例:
<?php
$map = new WeakMap();
$obj = new stdClass;
$map[$obj] = 1;
echo count($map) . PHP_EOL;
unset($obj);
echo count($map) . PHP_EOL;
这里的$map 有一个key$obj ,是一个stdClass 对象的引用。而使用unset($obj) 删除唯一“有效”的引用计数引用$obj 后,对应stdClass 的实例也就会被垃圾回收,此时$map 的key$obj 将变成一个无效引用,会自动从映射中删除,所以count($map) 输出是0。
Stringable
Stringable 接口只包含一个方法:
interface Stringable {
public __toString(): string
}
比较特别的是,所有实现了__toString 魔术方法的类都被看做是实现了Stringable 接口:
<?php
class MyClass
{
public function __toString()
{
return __CLASS__;
}
}
$mc = new MyClass;
var_dump($mc instanceof MyClass);
这大概是因为Stringable 接口本身只是php8.0.0加入的新接口,而__toString 方法早已存在许久的缘故。无论如何,还是推荐在php8.0.0以上版本的php中显式实现Stringable 接口。
UnitEnum
在PHP学习笔记15:枚举中提到过UnitEnum 接口:
interface UnitEnum {
public static cases(): array
}
所有的Enum 都默认实现了这个接口,这种实现是由Zend引擎实现的,而这个接口也并不能由用户直接用来实现或继承。其主要用途是类型检测:
<?php
enum Color
{
case RED;
case WHITE;
case BLACK;
}
var_dump(Color::BLACK instanceof UnitEnum);
要注意的是,instanceof 左侧的操作数必须是一个对象,虽然UnitEnum 接口定义的是静态方法,严格意义上来说应当使用Color instanceof UnitEnum 来判断,但这样做会产生一个语法错误,所以只能使用枚举对象。
BackedEnum
BackedEnum 是回退枚举隐式实现的接口:
interface BackedEnum extends UnitEnum {
public static from(string|int $value): static
public static tryFrom(string|int $value): ?static
}
它同样由Zend引擎直接实现,无法被用作类型判断以外的用途。具体的用法在PHP学习笔记15:枚举中有过说明,这里不再重复说明。
Countable
Countable 接口包含一个方法,用于返回包含的元素个数:
class Countable {
abstract public count(): int
}
Countable 属于SPL中的接口,而非直接属于php内核,但在比较新的php版本中,SPL扩展已经被默认启用。
示例:
<?php
class MyClass implements Countable
{
public function __construct(private array $arr)
{
}
public function count(): int
{
return count($this->arr);
}
}
$mc = new MyClass([1, 2, 3]);
echo count($mc);
OuterIterator
着同样是一个属于SPL扩展的接口:
interface OuterIterator extends Iterator {
public getInnerIterator(): ?Iterator
public Iterator::current(): mixed
public Iterator::key(): mixed
public Iterator::next(): void
public Iterator::rewind(): void
public Iterator::valid(): bool
}
这个接口是在Iterator 接口的基础上添加了一个返回迭代器的方法。这很像是Python中的Iterator 接口。其实更好的做法是让OuterIterator 接口同时实现Iterator 和IteratorAggregate 接口:
<?php
interface MyOuterIterator extends Iterator,IteratorAggregate{
}
class MyClass implements MyOuterIterator{
public function getIterator(): Traversable
{
return $this;
}
public function current(): mixed
{
}
public function next(): void
{
}
public function key(): mixed
{
}
public function rewind(): void
{
}
public function valid(): bool
{
}
}
至于为什么没有这么做,大概是历史遗留问题?
RecursiveIterator
这同样是一个SPL扩展接口,在Iterator 基础上添加了用于递归访问子元素的方法:
interface RecursiveIterator extends Iterator {
public getChildren(): ?RecursiveIterator
public hasChildren(): bool
public Iterator::current(): mixed
public Iterator::key(): mixed
public Iterator::next(): void
public Iterator::rewind(): void
public Iterator::valid(): bool
}
对于这个接口,我并没有想到相应的示例。
SeekIterator
也属于SPL扩展接口,在Iterator 接口基础上添加了一个seek 方法:
interface SeekableIterator extends Iterator {
public seek(int $offset): void
public Iterator::current(): mixed
public Iterator::key(): mixed
public Iterator::next(): void
public Iterator::rewind(): void
public Iterator::valid(): bool
}
官方手册的示例是seek 方法可以在游标越界后抛出一个OutOfBoundsException 类型的异常来说明遍历已经结束,但我比较疑惑的是Iterator 的valid 方法已经可以用来验证是否完成遍历,从功能上看两者是重复的。
最后我话费了“一点”时间绘制了以上接口和类的类图,如果要查看的话可以通过这里下载。
谢谢阅读。
往期内容
|