详解PHP的7个预定义接口

1. Traversable(遍历)接口

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

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的实例

class Test
{
}
$test = new Test;
var_dump($test instanceOf Traversable);

2. Iterator(迭代器)接口

迭代器接口其实实现的原理就是类似指针的移动,当我们写一个类的时候,通过实现对应的 5 个方法: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(聚合迭代器) 接口

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

<?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();
    }

    //从上面的代码,我们可以看到我们将Test类的对象传进去当做迭代器,通过while循环的话,我们必须通过调用getIterator()方法获取到迭代器对象,然后直接进行迭代输出,而不需要去实现相关的key()等方法。
    //当然这个时候,我们肯定想知道是否可以直接从foreach进行迭代循环出去呢?那么我们来打印一下结果

    $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(数组式访问)接口

通常情况下,我们会看到 this ['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 () 的话,我们在进行 serialize () 的时候,会先调用sleep () 的魔术方法,我们通过返回一个数组,来定义对对象的哪些属性进行序列化,同理,我们在调用反序列化 unserialize () 方法的时候,也会先调用的wakeup()魔术方法,我们可以进行初始化,如对一个对象的属性进行赋值等操作;但是如果该类实现了序列化接口,我们就必须实现 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));

    //实现序列化接口,发现魔术方法失效了

   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 () 是个静态方法,具体用法看如下;

<?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;
    }

    $func = $closure->bindTo(new Test);
    $func();
    // 这个是可以访问不到私有属性的,会报出无法访问私有属性
    // 下面这个是正确的做法
    $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。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);

输出:

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);

输出:

memory: 6680312 time: 0.10804104804993

以上就是详解PHP的7个预定义接口的详细内容,更多关于PHP的7个预定义接口的资料请关注我们其它相关文章!

(0)

相关推荐

  • PHP中预定义的6种接口介绍

    PHP预定义了6个接口介绍如下: 1.Traversable遍历接口 呵呵!其实它不是一个在PHP中可以使用的接口,内部类才可使用,它有一个用途就是检测一个类是否可以遍历. if($class instanceof Traversable) { //foreach } 2.Iterator迭代器接口 接口摘要: Iterator extends Traversable { //返回当前索引游标指向的元素 abstract public mixed current(void) //返回当前索引游标

  • PHP教程 预定义变量

    这些数组非常特别,它们在全局范围内自动生效,例如,在任何范围内自动生效.因此通常被称为自动全局变量(autoglobals)或者超全局变量(superglobals). 复制代码 代码如下: <?php $a = "PHP教程网"; echo "通过\$GLOBALS来取变量值:".$GLOBALS['a']; //输出:通过$GLOBALS来取变量值:PHP教程网 echo "<br/>"; echo "当前执行脚本

  • php学习笔记 [预定义数组(超全局数组)]

    复制代码 代码如下: <?php /* 预定义数组: * 自动全局变量---超全局数组 * * 1.包含了来自WEB服务器,客户端,运行环境和用户输入的数据 * 2.这些数组比较特别 * 3.全局范围内自动生效,都可以直接使用这些数组 * 4.用户不能自定义这些数组,但这些数组的操作方式和自己定义的数组操作的方式一样 * 5.在函数中直接可以使用这些数组 * * $_GET //经由URL请求提交至脚本的变量 * $_POST //经由HTTP POST 方法提交到脚本的变量 * $_REQUE

  • PHP预定义接口——Iterator用法示例

    本文实例讲述了PHP预定义接口--Iterator用法.分享给大家供大家参考,具体如下: Iterator(迭代器)接口 可在内部迭代自己的外部迭代器或类的接口. 接口摘要 Iterator extends Traversable { /* 方法 */ abstract public current ( void ) : mixed abstract public key ( void ) : scalar abstract public next ( void ) : void abstrac

  • PHP 预定义变量、魔术常量和魔术方法功能与用法小结

    本文总结了PHP 预定义变量.魔术常量和魔术方法.分享给大家供大家参考,具体如下: PHP 预定义了一些变量.常量.方法,使用起来很方便,例如直接使用 __DIR__ 获取当前文件所在的目录. 下面分别详细介绍一下 PHP 的预定义变量.魔术常量和魔术方法 预定义变量 超全局变量: 超全局变量是在全部作用域中始终可用的内置变量,意味这你可以在 PHP 脚本的任意位置直接访问.在函数或方法中无需执行 global $variable; 就可以访问它们. 超全局变量是从 PHP 4.1.0 开始才有

  • php中常用的预定义变量小结

    复制代码 代码如下: <?php echo "当前操作系统信息".PHP_OS."<br/>"; echo '本文件路径和文件名为:'.__FILE__.'<br />'; echo "当前PHP版本信息".PHP_VERSION."<br/>"; ?> 常用的php预定义变量! 前面的$_SERVER[PHP_SELF]返回的是一段信息,如"/upload/try.p

  • 不常用但很实用的PHP预定义变量分析

    1. $php_errormsg - 前一个错误信息 <?php @strpos(); echo $php_errormsg; ?> 2.$http_response_header - HTTP 响应头 <?php function get_contents() { file_get_contents("http://example.com"); var_dump($http_response_header); } get_contents(); var_dump($

  • PHP之预定义接口详解

    在PHP中有好几个预定义的接口,比较常用的四个接口(IteratorAggregate(聚合式aggregate迭代器Iterator).Countable.ArrayAccess.Iterator)分别给大家详细介绍下. IteratorAggregate(聚合式aggregate迭代器Iterator)接口 复制代码 代码如下: IteratorAggregate extends Traversable {  abstract public Traversable getIterator(v

  • 详解PHP的7个预定义接口

    1. Traversable(遍历)接口 该接口不能被类直接实现,如果直接写了一个普通类实现了该遍历接口,是会直接报致命的错误,提示使用 Iterator(迭代器接口)或者 IteratorAggregate(聚合迭代器接口)来实现,这两个接口后面会介绍:所有通常情况下,我们只是会用来判断该类是否可以使用 foreach 来进行遍历: class Test implements Traversable { } 上面这个是错误示范,该代码会提示这样的错误: Fatal error: Class T

  • 详解mybatis-plus配置找不到Mapper接口路径的坑

    mybatis-plus今天遇到一个问题,就是mybatis 没有读取到mapper.xml 文件. 特此记录一下,问题如下: at com.baomidou.mybatisplus.core.override.MybatisMapperMethod$SqlCommand.<init>(MybatisMapperMethod.java:242) at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.<init>(

  • 详解Spring Data JPA中Repository的接口查询方法

    目录 1.查询方法定义详解 2.搜索查询策略 3.查询创建 4.属性表达式 5.特殊参数处理 6.限制查询结果 7. repository方法返回Collections or Iterables 8.repository方法处理Null 9.查询结果流 10.异步查询结果 1.查询方法定义详解 repository代理有两种方式从方法名中派生出特定存储查询. 通过直接从方法名派生查询. 通过使用一个手动定义的查询. 可用的选项取决于实际的商店.然而,必须有一个策略来决定创建什么实际的查询. 2.

  • 详解PHP的抽象类和抽象方法以及接口总结

    PHP中的抽象类和抽象方法自己用的不多,但是经常会在项目中看到别人使用,同样,今天在看别人的代码的时候,发现使用了抽象类,就总结下: 抽象类: 1.如果一个类中有一个方法是抽象方法,则这个类就是抽象类: 2.抽象类必须加上abstract关键字修饰: 抽象方法: 定义:一个方法如果没有方法体,则这个方法就是抽象方法 1.一个方法没有方法体,直接用分号结束的方法: 2.如果是抽象方法,必须使用abstract抽象关键字形容 注意:1.只要有abstract修饰的类就是抽象类   2.除了在抽象类中

  • 详解vue-cli 构建项目 vue-cli请求后台接口 vue-cli使用axios、sass、swiper

    1.vue-cli 构建项目 //全局安装 vue-cli npm install - g vue- cli //创建一个基于 webpack 模板的新项目 vue init webpack my- project //cd 进入刚新建的项目文件(my-project) cd my- project // 运行 npm run dev 2.vue-cli 使用axios //1.先安装axios npm install axios //2.main.js中引入并放在Vue的原型上 import

  • 详解vue中使用axios对同一个接口连续请求导致返回数据混乱的问题

    业务上出现一个问题:如果连续对同一个接口发出请求,参数不同,有时候先请求的比后请求的返回数据慢,导致数据顺序混乱,或者数据被覆盖的问题,所以需要控制请求的顺序. 解决方法: 1.直接跟后台沟通,将所有参数放到数组里后台统一接收并返回所有数据再由前端进行数据的拆分使用. 2.对于出现返回的数据混乱问题. 假设场景: 页面中需要对三个部门请求对应的部门人员,三个部门人员的数据为一个二维数组,连续发送请求,但由于返回数据的顺序不定,导致数组中的数据顺序不是按照部门的顺序. 解决方法:使用promise

  • PHP中Closure类的使用方法及详解

    Closure,匿名函数,又称为Anonymous functions,是php5.3的时候引入的.匿名函数就是没有定义名字的函数.这点牢牢记住就能理解匿名函数的定义了. Closure 类(PHP 5 >= 5.3.0)简介 用于代表 匿名函数 的类. 匿名函数(在 PHP 5.3 中被引入)会产生这个类型的对象,下面我们来看一下PHP Closure类的使用方法及介绍. PHP Closure类之前在PHP预定义接口中介绍过,但它可不是interface哦,它是一个内部的final类.Clo

  • Java中的引用和动态代理的实现详解

    我们知道,动态代理(这里指JDK的动态代理)与静态代理的区别在于,其真实的代理类是动态生成的.但具体是怎么生成,生成的代理类包含了哪些内容,以什么形式存在,它为什么一定要以接口为基础? 如果去看动态代理的源代码(java.lang.reflect.Proxy),会发现其原理很简单(真正二进制类文件的生成是在本地方法中完成,源代码中没有),但其中用到了一个缓冲类java.lang.reflect.WeakCache<ClassLoader,Class<?>[],Class<?>

随机推荐