深入学习7个PHP预定义接口
我们倾向于在日常工作中编写业务模块,但很少实现这样的接口,但接口在框架中使用了很多。
1. Traversable
接口
该接口不能由类直接实现。如果你编写一个普通的类Traversable
直接实现接口,它将直接报告致命错误,提示使用Iterator
或IteratorAggregate
实现。稍后将介绍这两个接口,通常用于确定是否可以通过使用遍历类foreach
;
class Test implements Traversable
{
}
以上是一个错误的例子,将提示这样的错误:
致命错误:类测试必须实现接口
Traversable
作为其中一部分Iterator
或IteratorAggregate
在第0行的未知中
正确的方法:
如果我们想确定一个类是否可以foreach
用来遍历,我们只需要确定它是否是可遍历的实例。
class Test
{
}
$test = new Test;
var_dump($test instanceOf Traversable);
2. Iterator
接口
Iterator
接口的实际实现类似于指针的移动。当我们正在写一类,我们通过实施相应的五个方法来实现数据的反复运动:key()
,current()
,next()
,rewind()
,valid()
。这是代码。
<?php
class Test implements Iterator
{
private $key;
private $val = [
'one',
'two',
'three',
];
public function key()
{
return $this->key;
}
public function current()
{
return $this->val[$this->key];
}
public function next()
{
++$this->key;
}
public function rewind()
{
$this->key = 0;
}
public function valid()
{
return isset($this->val[$this->key]);
}
}
$test = new Test;
$test->rewind();
while($test->valid()) {
echo $test->key . ':' . $test->current() . PHP_EOL;
$test->next();
}
输出结果:
0: one
1: two
2: three
事实上,迭代的移动方法是:rewind()
- > valid()
- > key()
- > current()
- > next()
- > valid()
- > key()
....-> valid()
;
现在让我们看一下Iterator
接口,发现它实现了Traversable
接口。让我们证明这一点:
var_dump($test instanceOf Traversable);
如果返回的结果为true,则类的对象是可遍历的。
foreach ($test as $key => $value){
echo $test->key . ':' . $test->current() . PHP_EOL;
}
结果与while循环实现的模式相同。
3. IteratorAggregate
接口
为原则IteratorAggregate
,并Iterator
是一样的。您只需要实现一个getIterator()
方法来实现迭代,因为IteratorAggregate
它实现了迭代器原则。这是代码。
<?php
class Test implements IteratorAggregate
{
public $one = 1;
public $two = 2;
public $three = 3;
public function __construct()
{
$this->four = 4;
}
public function getIterator()
{
return new AraayIterator($this);
}
}
$test = (new Test())->getIterator();
$test->rewind();
while($test->valid()) {
echo $test->key() . ' : ' . $test->current() . PHP_EOL;
$test->next();
}
从上面的代码中,我们可以看到,我们传递的对象类的迭代器。通过while循环,我们必须通过调用来获取迭代器对象的方法,然后进行迭代,并直接输出而不去执行相关的方法,如。Test``getIterator()``key()
然后让我们打印结果:
$test = new Test;
var_dump($test instanceOf Traversable);
它输出bool true
。接下来我们将使用foreach
它直接实现它。
$test = new Test;
foreach($test as $key => $value) {
echo $key . ' : ' . $value . PHP_EOL;
}
我们可以迭代对象,迭代数组怎么样?
class Test implements IteratorAggregate
{
public $data;
public function __construct()
{
$this->data = ['one' => 1 , 'two' => 2];
}
public function getIterator()
{
return new AraayIterator($this->data);
}
}
实现它的方法是一样的。
4. ArrayAccess
接口
这个['name']的用法很常见。既然$this
是一个对象,那么如何使用数组进行访问?答案是ArrayAccess
接口。具体代码如下:
<?php
class Test implements ArrayAccess
{
public $container;
public function __construct()
{
$this->container = [
'one' => 1,
'two' => 2,
'three' => 3,
];
}
public function offsetExists($offset)
{
return isset($this->container[$offset]);
}
public function offsetGet($offset)
{
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetUnset($offset)
{
unset($this->container[$offset]);
}
}
$test = new Test;
var_dump(isset($test['one']));
var_dump($test['two']);
unset($test['two']);
var_dump(isset($test['two']));
$test['two'] = 22;
var_dump($test['two']);
$test[] = 4;
var_dump($test);
var_dump($test[0]);
当然,有一种经典的方法是将对象的属性作为数组访问。
class Test implements ArrayAccess
{
public $name;
public function __construct()
{
$this->name = 'gabe';
}
public function offsetExists($offset)
{
return isset($this->$offset);
}
public function offsetGet($offset)
{
return isset($this->$offset) ? $this->$offset : null;
}
public function offsetSet($offset, $value)
{
$this->$offset = $value;
}
public function offsetUnset($offset)
{
unset($this->$offset);
}
}
$test = new Test;
var_dump(isset($test['name']));
var_dump($test['name']);
var_dump($test['age']);
$test[1] = '22';
var_dump($test);
unset($test['name']);
var_dump(isset($test['name']));
var_dump($test);
$test[] = 'hello world';
var_dump($test);
5. Serializable
接口
通常情况下,如果有魔术方法sleep()
,wakeup()
在类中定义的,它会调用的魔术方法sleep()
执行时第一serialize()
。我们通过返回一个数组来定义对象的哪些属性被序列化。同样,它也会wakeup()
在调用方法时首先调用magic unserialize()
方法。我们可以初始化魔术方法,例如为对象的属性赋值; 但如果类实现了Serialization
接口,我们必须实现serialize()
和unserialize()
,同时,无论是sleep()
也wakeup()
将得到支持。具体代码如下:
<?php
class Test
{
public $name;
public $age;
public function __construct()
{
$this->name = 'gabe';
$this->age = 25;
}
public function __wakeup()
{
var_dump(__METHOD__);
$this->age++;
}
public function __sleep()
{
var_dump(__METHOD__);
return ['name'];
}
}
$test = new Test;
$a = serialize($test);
var_dump($a);
var_dump(unserialize($a));
//You'll find that the magic methods have failed after implementing the Serialization interface.
class Test implements Serializable
{
public $name;
public $age;
public function __construct()
{
$this->name = 'gabe';
$this->age = 25;
}
public function __wakeup()
{
var_dump(__METHOD__);
$this->age++;
}
public function __sleep()
{
var_dump(__METHOD__);
return ['name'];
}
public function serialize()
{
return serialize($this->name);
}
public function unserialize($serialized)
{
$this->name = unserialize($serialized);
$this->age = 1;
}
}
$test = new Test;
$a = serialize($test);
var_dump($a);
var_dump(unserialize($a));
6. Closure
类
任何匿名函数实际上都返回一个Closure
类的实例,并且类中主要有两个表示匿名函数的方法,bindTo()
和bind()
。如果检查源代码,您可以发现这两种方法实现了相同的目标,尽管bind()
是静态方法而bindTo()
不是。具体代码如下:
<?php
$closure = function () {
return 'hello world';
}
var_dump($closure);
var_dump($closure());
从上面的例子中,您可以看到第一个打印的是实例Closure
,第二个打印的是匿名函数返回的"hello world"字符串。接下来让我们使用这两种方法,两种方法的目的是将匿名函数绑定到类。
bindTo()
<?php
namespace demo1;
class Test {
private $name = 'hello woeld';
}
$closure = function () {
return $this->name;
}
// This is the wrong approach
$func = $closure->bindTo(new Test);
$func();
// This is the right approach
$func = $closure->bindTo(new Test, Test::class);
$func();
namespace demo2;
class Test
{
private statis $name = 'hello world';
}
$closure = function () {
return self::$name;
}
$func = $closure->bindTo(null, Test::class);
$func();
bind()
<?php
namespace demo1;
class Test
{
private $name = 'hello world';
}
$func = \Closure::bind(function() {
return $this->name;
}, new Test, Test::class);
$func();
namespace demo2;
class Test
{
private static $name = 'hello world';
}
$func = \Closure::bind(function() {
return self::$name;
}, null, Test::class);
$func()
7.Generator
Generator实现了Iterator
,并且还生成了实例,但它不能被继承。由于Iterator
实现了,如上所述,它具有与以下内容相同的功能Iterator
:rewind
- > valid
- > current
- > key
- > next
....语法Generator
主要来自yield
记录活动轨迹并返回实例的关键字Generator
。
优点Generator
是当我们需要遍历大数据或读写大文件时,如果我们的内存不够,那么我们可以大大减少内存消耗。传统的遍历将返回所有数据并将它们存储到内存中,而yield
只返回当前值。但是当我们使用yield时会有一个处理内存的过程,所以实际上这是一种交换空间的方法。
<?php
$start_time = microtime(true);
function xrange(int $num){
for($i = 0; $i < $num; $i++) {
yield $i;
}
}
$generator = xrange(100000);
foreach ($generator as $key => $value) {
echo $key . ': ' . $value . PHP_EOL;
}
echo 'memory: ' . memory_get_usage() . ' time: '. (microtime(true) - $start_time);
Output: memory: 388904 time: 0.12135100364685
<?php
$start_time = microtime(true);
function xrange(int $num){
$arr = [];
for($i = 0; $i < $num; $i++) {
array_push($arr, $i);
}
return $arr;
}
$arr = xrange(100000);
foreach ($arr as $key => $value) {
echo $key . ': ' . $value . PHP_EOL;
}
echo 'memory: ' . memory_get_usage() . ' time: '. (microtime(true) - $start_time);
Output: memory: 6680312 time: 0.10804104804993
译文地址: https://www.tutorialdocs.com/article/7-php-predefined-interfaces.html