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

深入理解PHP中七个预定义接口

发布:smiling 来源: PHP粉丝网  添加日期:2022-05-31 10:42:55 浏览: 评论:0 

深入理解预定义接口

场景:平常工作中写的都是业务模块,很少会去实现这样的接口,但是在框架里面用的倒是很多。

1. Traversable(遍历)接口

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

  1. class Test implements Traversable 
  2.  
  3.     { 
  4.  
  5.     } 

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

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.  
  3.  
  4.  
  5. $test = new Test; 
  6.  
  7. var_dump($test instanceOf Traversable); 

2. Iterator(迭代器)接口

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

  1. <?php 
  2.  
  3.     class Test implements Iterator 
  4.  
  5.     { 
  6.  
  7.         private $key
  8.  
  9.         private $val = [ 
  10.  
  11.             'one'
  12.  
  13.             'two'
  14.  
  15.             'three'
  16.  
  17.         ]; 
  18.  
  19.  
  20.  
  21.         public function key() 
  22.  
  23.         { 
  24.  
  25.             return $this->key; 
  26.  
  27.         } 
  28.  
  29.  
  30.  
  31.         public function current() 
  32.  
  33.         { 
  34.  
  35.             return $this->val[$this->key]; 
  36.  
  37.         } 
  38.  
  39.  
  40.  
  41.         public function next() 
  42.  
  43.         { 
  44.  
  45.             ++$this->key; 
  46.  
  47.         } 
  48.  
  49.  
  50.  
  51.         public function rewind() 
  52.  
  53.         { 
  54.  
  55.             $this->key = 0; 
  56.  
  57.         } 
  58.  
  59.  
  60.  
  61.         public function valid() 
  62.  
  63.         { 
  64.  
  65.             return isset($this->val[$this->key]); 
  66.  
  67.         } 
  68.  
  69.     } 
  70.  
  71.  
  72.  
  73.     $test = new Test; 
  74.  
  75.  
  76.  
  77.     $test->rewind(); 
  78.  
  79.  
  80.  
  81.     while($test->valid()) { 
  82.  
  83.         echo $test->key . ':' . $test->current() . PHP_EOL; 
  84.  
  85.         $test->next(); 
  86.  
  87.     } 

## 该输出结果 :

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.  
  3.         echo $test->key . ':' . $test->current() . PHP_EOL; 
  4.  
  5.  } 

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

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

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

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

从上面的代码,我们可以看到我们将Test类的对象传进去当做迭代器,通过while循环的话,我们必须通过调用getIterator()方法获取到迭代器对象,然后直接进行迭代输出,而不需要去实现相关的key()等方法。

当然这个时候,我们肯定想知道是否可以直接从foreach进行迭代循环出去呢?那么我们来打印一下结果:

$test = new Test;

var_dump($test instanceOf Traversable);

结果是输出bool true,所以我们接下来是直接用foreach来实现一下。

  1. $test = new Test; 
  2.  
  3. reach($test as $key => $value) { 
  4.  
  5.  echo $key . ' : ' . $value  .  PHP_EOL; 

接下来,我们看到是对对象进行迭代,这个时候我们是否可以数组进行迭代呢?

  1. class Test implements IteratorAggregate 
  2.  
  3.  
  4.    public $data
  5.  
  6.  
  7.  
  8.    public function __construct() 
  9.  
  10.    { 
  11.  
  12.        $this->data = [''one' =>  1 , 'two' => 2]; 
  13.  
  14.    } 
  15.  
  16.  
  17.  
  18.    public function getIterator() 
  19.  
  20.    { 
  21.  
  22.        return new AraayIterator($this->data); 
  23.  
  24.    } 
  25.  

同理实现的方式跟对对象进行迭代是一样的。

很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的加群(点击→)677079770

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

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

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

当然我们也有经典的一个做法就是把对象的属性当做数组来访问:

  1.  class Test implements ArrayAccess 
  2.  
  3.  { 
  4.  
  5.       public $name
  6.  
  7.  
  8.  
  9.       public function __construct() 
  10.  
  11.       { 
  12.  
  13.           $this->name = 'gabe';   
  14.  
  15.       } 
  16.  
  17.  
  18.  
  19.       public function offsetExists($offset
  20.  
  21.       { 
  22.  
  23.           return isset($this->$offset); 
  24.  
  25.       } 
  26.  
  27.  
  28.  
  29.       public function offsetGet($offset
  30.  
  31.       { 
  32.  
  33.           return isset($this->$offset) ? $this->$offset : null; 
  34.  
  35.       } 
  36.  
  37.  
  38.  
  39.       public function offsetSet($offset$value
  40.  
  41.       { 
  42.  
  43.           $this->$offset = $value
  44.  
  45.       } 
  46.  
  47.  
  48.  
  49.       public function offsetUnset($offset
  50.  
  51.       { 
  52.  
  53.           unset($this->$offset); 
  54.  
  55.       } 
  56.  
  57.  } 
  58.  
  59.  
  60.  
  61. $test = new Test; 
  62.  
  63. var_dump(isset($test['name'])); 
  64.  
  65. var_dump($test['name']); 
  66.  
  67. var_dump($test['age']); 
  68.  
  69. $test[1] = '22'
  70.  
  71. var_dump($test); 
  72.  
  73. unset($test['name']); 
  74.  
  75. var_dump(isset($test['name'])); 
  76.  
  77. var_dump($test); 
  78.  
  79. $test[] = 'hello world'
  80.  
  81. var_dump($test); 

5. Serializable (序列化)接口

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

  1. <?php 
  2.  
  3.     class Test 
  4.  
  5.     {     
  6.  
  7.         public $name
  8.  
  9.         public $age
  10.  
  11.  
  12.  
  13.         public function __construct() 
  14.  
  15.         { 
  16.  
  17.             $this->name = 'gabe'
  18.  
  19.             $this->age = 25;  
  20.  
  21.         }     
  22.  
  23.  
  24.  
  25.         public function __wakeup() 
  26.  
  27.         { 
  28.  
  29.             var_dump(__METHOD__);  
  30.  
  31.             $this->age++; 
  32.  
  33.         }    
  34.  
  35.  
  36.  
  37.         public function __sleep() 
  38.  
  39.         {         
  40.  
  41.             var_dump(__METHOD__); 
  42.  
  43.             return ['name'];     
  44.  
  45.         } 
  46.  
  47.     } 
  48.  
  49.  
  50.  
  51.     $test = new Test; 
  52.  
  53.     $a = serialize($test); 
  54.  
  55.     var_dump($a); 
  56.  
  57.     var_dump(unserialize($a)); 
  58.  
  59.  
  60.  
  61.     //实现序列化接口,发现魔术方法失效了 
  62.  
  63.  
  64.  
  65.    class Test implements Serializable 
  66.  
  67.    {     
  68.  
  69.     public $name
  70.  
  71.     public $age
  72.  
  73.  
  74.  
  75.     public function __construct() 
  76.  
  77.     {         
  78.  
  79.         $this->name = 'gabe'
  80.  
  81.         $this->age = 25; 
  82.  
  83.     }  
  84.  
  85.  
  86.  
  87.     public function __wakeup() 
  88.  
  89.     {  
  90.  
  91.         var_dump(__METHOD__); 
  92.  
  93.         $this->age++; 
  94.  
  95.     } 
  96.  
  97.  
  98.  
  99.     public function __sleep() 
  100.  
  101.     { 
  102.  
  103.         var_dump(__METHOD__); 
  104.  
  105.         return ['name']; 
  106.  
  107.     } 
  108.  
  109.  
  110.  
  111.     public function serialize() 
  112.  
  113.     { 
  114.  
  115.         return serialize($this->name); 
  116.  
  117.     }  
  118.  
  119.  
  120.  
  121.     public function unserialize($serialized
  122.  
  123.     {        
  124.  
  125.         $this->name = unserialize($serialized); 
  126.  
  127.         $this->age = 1;     
  128.  
  129.     } 
  130.  
  131.  
  132. $test = new Test; 
  133.  
  134. $a = serialize($test); 
  135.  
  136. var_dump($a); 
  137.  
  138. var_dump(unserialize($a)); 

6. Closure 类

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

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

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

bindTo()

  1. <?php 
  2.  
  3. namespace demo1; 
  4.  
  5.     class Test { 
  6.  
  7.         private $name = 'hello woeld'
  8.  
  9.     } 
  10.  
  11.  
  12.  
  13.     $closure = function () { 
  14.  
  15.         return $this->name; 
  16.  
  17.     } 
  18.  
  19.  
  20.  
  21.     $func = $closure->bindTo(new Test); 
  22.  
  23.     $func(); 
  24.  
  25.     // 这个是可以访问不到私有属性的,会报出无法访问私有属性 
  26.  
  27.     // 下面这个是正确的做法 
  28.  
  29.     $func = $closure->bindTo(new Test, Test::class); 
  30.  
  31.     $func(); 
  32.  
  33.  
  34.  
  35. namespace demo2; 
  36.  
  37.     class Test 
  38.  
  39.     { 
  40.  
  41.         private statis $name = 'hello world'
  42.  
  43.     } 
  44.  
  45.  
  46.  
  47.     $closure = function () { 
  48.  
  49.         return self::$name
  50.  
  51.     } 
  52.  
  53.     $func = $closure->bindTo(null, Test::class); 
  54.  
  55.     $func(); 
  56.  
  57. bind()          
  58.  
  59. <?php 
  60.  
  61. namespace demo1; 
  62.  
  63. class Test  
  64.  
  65.  
  66.     private  $name = 'hello world'
  67.  
  68.  
  69.  
  70.  
  71. $func = \Closure::bind(function() { 
  72.  
  73.     return $this->name; 
  74.  
  75. }, new Test, Test::class); 
  76.  
  77.  
  78.  
  79. $func(); 
  80.  
  81.  
  82.  
  83. namespace demo2; 
  84.  
  85. class Test  
  86.  
  87.  
  88.     private static  $name = 'hello world'
  89.  
  90.  
  91.  
  92.  
  93. $func = \Closure::bind(function() { 
  94.  
  95.     return self::$name
  96.  
  97. }, null, Test::class); 
  98.  
  99.  
  100.  
  101. $func() 

7. Generator (生成器)

Generator 实现了 Iterator,但是他无法被继承,同时也生成实例。既然实现了 Iterator,所以正如上文所介绍,他也就有了和 Iterator 相同的功能:rewind->valid->current->key->next...,Generator 的语法主要来自于关键字 yield。yield 就好比一次循环的中转站,记录本次的活动轨迹,返回一个 Generator 的实例。

Generator 的优点在于,当我们要使用到大数据的遍历,或者说大文件的读写,而我们的内存不够的情况下,能够极大的减少我们对于内存的消耗,因为传统的遍历会返回所有的数据,这个数据存在内存上,而 yield 只会返回当前的值,不过当我们在使用 yield 时,其实其中会有一个处理记忆体的过程,所以实际上这是一个用时间换空间的办法。

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

输出:memory: 388904 time: 0.12135100364685

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

输出:

memory: 6680312 time: 0.10804104804993

Tags: PHP预定义接口

分享到: