当前位置:首页 > PHP教程 > php高级应用 > 列表

详解PHP的7个预定义接口

发布:smiling 来源: PHP粉丝网  添加日期:2022-04-30 10:50:50 浏览: 评论:0 

本文介绍了PHP的7个预定义接口,感兴趣的同学,可以参考下,并且亲自试验一下,理解其原理。

1. Traversable(遍历)接口

该接口不能被类直接实现,如果直接写了一个普通类实现了该遍历接口,是会直接报致命的错误,提示使用 Iterator(迭代器接口)或者 IteratorAggregate(聚合迭代器接口)来实现,这两个接口后面会介绍;所有通常情况下,我们只是会用来判断该类是否可以使用 foreach 来进行遍历;

  1. class Test implements Traversable 

上面这个是错误示范,该代码会提示这样的错误:

Fatal error: Class Test must implement interface Traversable as part of either Iterator or

IteratorAggregate in Unknown on line 0

上面的大致意思是说如要实现这个接口,必须同Iterator或者IteratorAggregate来实现 正确的做法: 当我们要判断一个类是否可以使用foreach来进行遍历,只需要判断是否是traversable的实例:

  1. class Test 
  2. $test = new Test; 
  3. var_dump($test instanceOf Traversable); 

2. Iterator(迭代器)接口

迭代器接口其实实现的原理就是类似指针的移动,当我们写一个类的时候,通过实现对应的 5 个方法:key(),current(),next(),rewind(),valid(),就可以实现数据的迭代移动,具体看以下代码:

  1. <?php 
  2.     class Test implements Iterator 
  3.     { 
  4.         private $key
  5.         private $val = [ 
  6.             'one'
  7.             'two'
  8.             'three'
  9.         ]; 
  10.  
  11.         public function key() 
  12.         { 
  13.             return $this->key; 
  14.         } 
  15.  
  16.         public function current() 
  17.         { 
  18.             return $this->val[$this->key]; 
  19.         } 
  20.  
  21.         public function next() 
  22.         { 
  23.             ++$this->key; 
  24.         } 
  25.  
  26.         public function rewind() 
  27.         { 
  28.             $this->key = 0; 
  29.         } 
  30.  
  31.         public function valid() 
  32.         { 
  33.             return isset($this->val[$this->key]); 
  34.         } 
  35.     } 
  36.  
  37.     $test = new Test; 
  38.  
  39.     $test->rewind(); 
  40.  
  41.     while($test->valid()) { 
  42.         echo $test->key . ':' . $test->current() . PHP_EOL; 
  43.         $test->next(); 
  44.     } 

## 该输出结果:   

0: one

1: two

2: three

看了这个原理我们就知道,其实迭代的移动方式:rewind()-> valid()->key() -> current() -> next() -> valid()-> key() ....-> valid();

好的,理解了上面,我们打开Iterator的接口,发现它是实现了Traversable(遍历)接口的,接下来我们来证明下:

var_dump($test instanceOf Traversable);

结果返回的是true,证明这个类的对象是可以进行遍历的。

  1. foreach ($test as $key => $value){ 
  2.     echo $test->key . ':' . $test->current() . PHP_EOL; 

这个的结果跟while循环实现的模式是一样的。

3. IteratorAggregate(聚合迭代器) 接口

聚合迭代器和迭代器的原理是一样的,只不过聚合迭代器已经实现了迭代器原理,你只需要实现一个 getIterator()方法来实现迭代,具体看以下代码:

  1. <?php 
  2.     class Test implements IteratorAggregate 
  3.     { 
  4.         public $one = 1; 
  5.         public $two = 2; 
  6.         public $three = 3; 
  7.  
  8.         public function __construct() 
  9.         { 
  10.             $this->four = 4; 
  11.         } 
  12.  
  13.         public function getIterator() 
  14.         { 
  15.             return new AraayIterator($this); 
  16.         } 
  17.     } 
  18.  
  19.     $test = (new Test())->getIterator(); 
  20.     $test->rewind(); 
  21.     while($test->valid()) { 
  22.         echo $test->key() . ' : '  .  $test->current() . PHP_EOL; 
  23.         $test->next(); 
  24.     } 
  25.  
  26.     //从上面的代码,我们可以看到我们将Test类的对象传进去当做迭代器,通过while循环的话,我们必须通过调用getIterator()方法获取到迭代器对象,然后直接进行迭代输出,而不需要去实现相关的key()等方法。 
  27.     //当然这个时候,我们肯定想知道是否可以直接从foreach进行迭代循环出去呢?那么我们来打印一下结果 
  28.  
  29.     $test = new Test; 
  30.     var_dump($test instanceOf Traversable); 
  31.  
  32.     //结果是输出bool true,所以我们接下来是直接用foreach来实现一下。 
  33.  
  34.     $test = new Test; 
  35.   foreach($test as $key => $value) { 
  36.      echo $key . ' : ' . $value  .  PHP_EOL; 
  37.   } 
  38.  
  39.  //接下来,我们看到是对对象进行迭代,这个时候我们是否可以数组进行迭代呢? 
  40.  
  41.  class Test implements IteratorAggregate 
  42.  { 
  43.     public $data
  44.  
  45.     public function __construct() 
  46.     { 
  47.         $this->data = [''one' =>  1 , 'two' => 2]; 
  48.     } 
  49.  
  50.     public function getIterator() 
  51.     { 
  52.         return new AraayIterator($this->data); 
  53.     } 
  54.  } 
  55.  
  56.  //同理实现的方式跟对对象进行迭代是一样的。 

4.ArrayAccess(数组式访问)接口

通常情况下,我们会看到 this ['name'] 这样的用法,但是我们知道,$this 是一个对象,是如何使用数组方式访问的?答案就是实现了数据组访问接口 ArrayAccess,具体代码如下:

  1. <?php 
  2.     class Test implements ArrayAccess 
  3.     { 
  4.         public $container
  5.  
  6.         public function __construct() 
  7.         { 
  8.             $this->container = [ 
  9.                 'one' => 1, 
  10.                 'two' => 2, 
  11.                 'three'  => 3, 
  12.             ]; 
  13.         } 
  14.  
  15.         public function offsetExists($offset
  16.         { 
  17.             return isset($this->container[$offset]); 
  18.         } 
  19.  
  20.         public function offsetGet($offset
  21.         { 
  22.             return isset($this->container[$offset]) ? $this->container[$offset] : null; 
  23.         } 
  24.  
  25.         public function offsetSet($offset$value
  26.         { 
  27.             if (is_null($offset)) { 
  28.                 $this->container[] = $value
  29.             } else { 
  30.                 $this->container[$offset] = $value
  31.             } 
  32.         } 
  33.  
  34.         public function offsetUnset($offset
  35.         { 
  36.             unset($this->container[$offset]); 
  37.         } 
  38.     } 
  39.    $test = new Test; 
  40.    var_dump(isset($test['one'])); 
  41.    var_dump($test['two']); 
  42.    unset($test['two']); 
  43.    var_dump(isset($test['two'])); 
  44.    $test['two'] = 22; 
  45.    var_dump($test['two']); 
  46.    $test[] = 4; 
  47.    var_dump($test); 
  48.    var_dump($test[0]); 
  49.  
  50.    //当然我们也有经典的一个做法就是把对象的属性当做数组来访问 
  51.  
  52.    class Test implements ArrayAccess 
  53.    { 
  54.         public $name
  55.  
  56.         public function __construct() 
  57.         { 
  58.             $this->name = 'gabe'
  59.         } 
  60.  
  61.         public function offsetExists($offset
  62.         { 
  63.             return isset($this->$offset); 
  64.         } 
  65.  
  66.         public function offsetGet($offset
  67.         { 
  68.             return isset($this->$offset) ? $this->$offset : null; 
  69.         } 
  70.  
  71.         public function offsetSet($offset$value
  72.         { 
  73.             $this->$offset = $value
  74.         } 
  75.  
  76.         public function offsetUnset($offset
  77.         { 
  78.             unset($this->$offset); 
  79.         } 
  80.    } 
  81.  
  82.   $test = new Test; 
  83.   var_dump(isset($test['name'])); 
  84.   var_dump($test['name']); 
  85.   var_dump($test['age']); 
  86.   $test[1] = '22'
  87.   var_dump($test); 
  88.   unset($test['name']); 
  89.   var_dump(isset($test['name'])); 
  90.   var_dump($test); 
  91.   $test[] = 'hello world'
  92.   var_dump($test); 

5. Serializable (序列化)接口

通常情况下,如果我们的类中定义了魔术方法,sleep(),wakeup () 的话,我们在进行 serialize () 的时候,会先调用sleep () 的魔术方法,我们通过返回一个数组,来定义对对象的哪些属性进行序列化,同理,我们在调用反序列化 unserialize () 方法的时候,也会先调用的wakeup()魔术方法,我们可以进行初始化,如对一个对象的属性进行赋值等操作;但是如果该类实现了序列化接口,我们就必须实现 serialize()方法和 unserialize () 方法,同时sleep()和wakeup () 两个魔术方法都会同时不再支持,具体代码看如下;

  1. <?php 
  2.     class Test 
  3.     { 
  4.         public $name
  5.         public $age
  6.  
  7.         public function __construct() 
  8.         { 
  9.             $this->name = 'gabe'
  10.             $this->age = 25; 
  11.         } 
  12.  
  13.         public function __wakeup() 
  14.         { 
  15.             var_dump(__METHOD__); 
  16.             $this->age++; 
  17.         } 
  18.  
  19.         public function __sleep() 
  20.         { 
  21.             var_dump(__METHOD__); 
  22.             return ['name']; 
  23.         } 
  24.     } 
  25.  
  26.     $test = new Test; 
  27.     $a = serialize($test); 
  28.     var_dump($a); 
  29.     var_dump(unserialize($a)); 
  30.  
  31.     //实现序列化接口,发现魔术方法失效了 
  32.  
  33.    class Test implements Serializable 
  34.    { 
  35.     public $name
  36.     public $age
  37.  
  38.     public function __construct() 
  39.     { 
  40.         $this->name = 'gabe'
  41.         $this->age = 25; 
  42.     } 
  43.  
  44.     public function __wakeup() 
  45.     { 
  46.         var_dump(__METHOD__); 
  47.         $this->age++; 
  48.     } 
  49.  
  50.     public function __sleep() 
  51.     { 
  52.         var_dump(__METHOD__); 
  53.         return ['name']; 
  54.     } 
  55.  
  56.     public function serialize() 
  57.     { 
  58.         return serialize($this->name); 
  59.     } 
  60.  
  61.     public function unserialize($serialized
  62.     { 
  63.         $this->name = unserialize($serialized); 
  64.         $this->age = 1; 
  65.     } 
  66. $test = new Test; 
  67. $a = serialize($test); 
  68. var_dump($a); 
  69. var_dump(unserialize($a)); 

6. Closure 类

用于代表匿名函数的类,凡是匿名函数其实返回的都是 Closure 闭包类的一个实例,该类中主要有两个方法,bindTo()和 bind(),通过查看源码,可以发现两个方法是殊途同归,只不过是 bind () 是个静态方法,具体用法看如下;

  1. <?php 
  2.     $closure = function () { 
  3.         return 'hello world'
  4.     } 
  5.  
  6.     var_dump($closure); 
  7.     var_dump($closure()); 

通过上面的例子,可以看出第一个打印出来的是 Closure 的一个实例,而第二个就是打印出匿名函数返回的 hello world 字符串;接下来是使用这个匿名类的方法,这两个方法的目的都是把匿名函数绑定一个类上使用;

  1. bindTo() 
  2.  
  3. <?php 
  4. namespace demo1; 
  5.     class Test { 
  6.         private $name = 'hello woeld'
  7.     } 
  8.  
  9.     $closure = function () { 
  10.         return $this->name; 
  11.     } 
  12.  
  13.     $func = $closure->bindTo(new Test); 
  14.     $func(); 
  15.     // 这个是可以访问不到私有属性的,会报出无法访问私有属性 
  16.     // 下面这个是正确的做法 
  17.     $func = $closure->bindTo(new Test, Test::class); 
  18.     $func(); 
  19.  
  20. namespace demo2; 
  21.     class Test 
  22.     { 
  23.         private statis $name = 'hello world'
  24.     } 
  25.  
  26.     $closure = function () { 
  27.         return self::$name
  28.     } 
  29.     $func = $closure->bindTo(null, Test::class); 
  30.     $func(); 
  31. bind() 
  32. <?php 
  33. namespace demo1; 
  34. class Test 
  35.     private  $name = 'hello world'
  36.  
  37. $func = \Closure::bind(function() { 
  38.     return $this->name; 
  39. }, new Test, Test::class); 
  40.  
  41. $func(); 
  42.  
  43. namespace demo2; 
  44. class Test 
  45.     private static  $name = 'hello world'
  46.  
  47. $func = \Closure::bind(function() { 
  48.     return self::$name
  49. }, null, Test::class); 
  50.  
  51. $func() 

7. Generator (生成器)

Generator 实现了 Iterator,但是他无法被继承,同时也生成实例。既然实现了 Iterator,所以正如上文所介绍,他也就有了和 Iterator 相同的功能:rewind->valid->current->key->next...,Generator 的语法主要来自于关键字 yield。yield 就好比一次循环的中转站,记录本次的活动轨迹,返回一个 Generator 的实例。Generator 的优点在于,当我们要使用到大数据的遍历,或者说大文件的读写,而我们的内存不够的情况下,能够极大的减少我们对于内存的消耗,因为传统的遍历会返回所有的数据,这个数据存在内存上,而 yield 只会返回当前的值,不过当我们在使用 yield 时,其实其中会有一个处理记忆体的过程,所以实际上这是一个用时间换空间的办法。

  1. <?php 
  2. $start_time = microtime(true); 
  3. function xrange(int $num){ 
  4.     for($i = 0; $i < $num$i++) { 
  5.         yield $i
  6.     } 
  7. $generator = xrange(100000); 
  8. foreach ($generator as $key => $value) { 
  9.     echo $key . ': ' . $value . PHP_EOL; 
  10. echo 'memory: ' . memory_get_usage() . ' time: '. (microtime(true) - $start_time); 

输出:

memory: 388904 time: 0.12135100364685

  1. <?php 
  2. $start_time = microtime(true); 
  3. function xrange(int $num){ 
  4.     $arr = []; 
  5.     for($i = 0; $i < $num$i++) { 
  6.         array_push($arr$i); 
  7.     } 
  8.     return $arr
  9. $arr = xrange(100000); 
  10. foreach ($arr as $key => $value) { 
  11.     echo $key . ': ' . $value . PHP_EOL; 
  12. echo 'memory: ' . memory_get_usage() . ' time: '. (microtime(true) - $start_time); 

输出:memory: 6680312 time: 0.10804104804993

Tags: PHP预定义接口

分享到: