PHP SPL 笔记(1) —— 简介 与 预定义接口

最近我开始学习PHP中的SPL库,这是PHP 5.3就添加的东西,但网上相关资料却不是很多。这里零散的记录自己的学习内容,目的是加深自己的印象,文中有错误的地方欢迎指出。

注:文章部分示例需在高版本PHP(5.6+)下运行


[TOC]

SPL简介

SPL是用于解决典型问题(standard problems)的一组接口与类的集合。

SPL(Standard PHP Library),中文翻译过来叫PHP标准库,它在PHP 5.3以后成为PHP内核组件的一部分,所以我们无需其他依赖就可以使用。

SPL是PHP内置的一组接口与类的集合,通常我们使用它来让Object拥有Array的特性。

Iterator (迭代器) 简介

Iterator是SPL的核心,它为不同对象提供了遍历的功能。例如,一个对象,一个文本每一行构成的集合,文件目录的集合,我们都可以利用Iterator实现像Array一样遍历。另外,在处理大数据量和特殊情况时,Iterator更具有优势(例如yield的使用)。

Interfaces (预定义接口)

在学习SPL类之前,我们先来学习几个预定义接口。

Traversable (遍历) 接口

Traversable 是作为所有可遍历接口的基础接口,目的是为了检测一个类是否可以使用foreach遍历。
Traversable 无法被单独实现,它必须由IteratorAggregateIterator 接口实现

示例:

// 大多情况下判断变量是否遍历
// (object)$items 可以被foreach遍历,但是无法通过下面检测
if( !is_array( $items ) && !$items instanceof Traversable ) {
  // 无法被遍历
}

注意array(object)array仍然可以被foreach遍历,但是他们并没有实现Traversable接口,所以一般情况下Traversable并不能作为foreach兼容性检测

Iterator (迭代器)

Iterator是SPL的核心,实现了外部迭代器可以迭代对象的接口。

interface Iterator extends Traversable {

    /**
     * 返回当前元素
     */
    public function current();

    /**
     * 向前移动到下一个元素
     */
    public function next();

    /**
     * 返回当前元素的键
     */
    public function key();

    /**
     * 检查当前位置是否有效
     */
    public function valid();

    /**
     * 返回到迭代器的第一个元素
     */
    public function rewind();
}

我们简单的实现一个迭代器:

// 可以打印每个方法,来查看何时调用的这个函数
class Test implements Iterator {

    private $position;
    private $array = [];

    public function __construct(array $array) {
        $this->position = 0;
        $this->array = $array;
    }


    function rewind() {
        // reset($this->array);
        $this->position = 0;
    }

    function current() {
        // return current($this->array);
        return $this->array[$this->position];
    }

    function key() {
        // return key($this->array);
        return $this->position;
    }

    function next() {
        // next($this->array);
        ++$this->position;
    }

    function valid() {
        // return !is_null(key($this->array));
        return isset($this->array[$this->position]);
    }

}

使用:

$test = new Test([
    'first',
    'second',
    'third'
]);

// foreach 迭代
foreach ($test as $key => $item) {
    var_dump($key . ' => ' . $item);
}

// while 迭代
$test->rewind();
while ($test->valid()) {
    var_dump($test->key() . ' => ' . $test->current());
    $test->next();
}

IteratorAggregate(聚合式迭代器)

IteratorAggregate 是创建外部迭代器的接口,必须实现getIterator方法

示例:

// 实现foreach循环输出拼接指定字符串
class Test implements \IteratorAggregate
{
    private $items = [];
    private $splice;

    public function __construct($items = [], $splice = '')
    {
        $this->items = $items;
        $this->splice = $splice;
    }

    public function getIterator()
    {
        $return = function() {
            while(list($key, $val) = each($this->items)) {
                yield $key => $val . $this->splice;
            }
        };
        return $return();
    }
}

使用:

$test = new Test([
    'first',
    'second',
    'third'
], 'c');

foreach ($test as $key => $item) {
    var_dump($key . ' => ' . $item);
}

// 打印,原数组不会改变
0 => firstc
1 => secondc
2 => thirdc

ArrayAccess(数组式访问)

利用 ArrayAccess 接口,可以让 Object 像 Array 一样操作。该接口需要实现4个方法。

interface ArrayAccess {

    /**
     * 检查一个偏移位置是否存在
     */
    public function offsetExists($offset);

    /**
     * 获取一个偏移位置的值
     */
    public function offsetGet($offset);

    /**
     * 设置一个偏移位置的值
     */
    public function offsetSet($offset, $value);

    /**
     * 复位一个偏移位置的值
     */
    public function offsetUnset($offset);
}

示例:

class Test implements \ArrayAccess
{
    private $data = [];

    // object 赋值调用
    public function __set($name, $value)
    {
        $this->data[$name] = $value;
    }

    // object 获取值调用
    public function &__get($name)
    {
       return $this->data[$name];
    }

    public function __isset($key) {
        return isset($this->data[$key]);
    }

    public function __unset($key) {
        unset($this->data[$key]);
    }

    public function offsetExists($offset)
   {
       return isset($this->data[$offset]);
   }

   public function offsetGet($offset)
   {
       return isset($this->data[$offset]) ? $this->data[$offset] : null;
   }

   public function offsetSet($offset, $value)
   {
       if (is_null($offset)) {
           $this->data[] = $value;
       } else {
           $this->data[$offset] = $value;
       }
   }

   public function offsetUnset($offset)
   {
       if ($this->offsetExists($offset)) {
           unset($this->data[$offset]);
       }
   }
}

使用:

// object可以像array一样获取/设置值
$test = new Test;
$test->name = 'John';
var_dump($test);
$test['name'] = 'Tom';
var_dump($test);
var_dump($test['name']);
var_dump($test->name);

// 打印
Lib\Test Object
(
    [data:Lib\Test:private] => Array
        (
            [name] => John
        )

)
Lib\Test Object
(
    [data:Lib\Test:private] => Array
        (
            [name] => Tom
        )

)
Tom
Tom

上面实现了数组式访问对象,但是并不能遍历这个对象,如果想同样遍历对象的话,可以在此接口上实现此前提到的IteratorIteratorAggregate接口。

Serializable(序列化)

序列化的接口,此接口必须实现两个方法:

interface Serializable {

    /**
     * 序列化
     */
    public function serialize();

    /**
     * 反序列化
     */
    public function unserialize($serialized);
}

Countable

Countable 接口实现了被count()函数计数,该接口必须实现1个方法

interface Countable {

    /**
     * 统计一个对象的元素个数
     */
    public function count();
}

RecursiveIterator

RecursiveIterator可用于递归迭代迭代器。也就是说RecursiveIterator用于遍历多层数据,它继承了Iterator,并且规定了hasChildren()getChildren()方法。

interface RecursiveIterator extends Iterator {

    /**
     * 返回是否可以为当前条目创建迭代器
     */
    public function hasChildren();

    /**
     * 返回当前条目的迭代器
     */
    public function getChildren();
}

SeekableIterator

SeekableIterator接口实现通过键值查找元素,它继承了Iterator,并规定了seek()方法。

interface SeekableIterator extends Iterator {
    public function seek($position);
}

示例:

// 在上部分Iterator基础上增加seek方法
class Test implements \SeekableIterator
{

    private $position;
    private $array = [];

    public function __construct(array $array) {
        $this->position = 0;
        $this->array = $array;
    }


    function rewind() {
        // reset($this->array);
        $this->position = 0;
    }

    function current() {
        // return current($this->array);
        return $this->array[$this->position];
    }

    function key() {
        // return key($this->array);
        return $this->position;
    }

    function next() {
        // next($this->array);
        ++$this->position;
    }

    function valid() {
        // return !is_null(key($this->array));
        return isset($this->array[$this->position]);
    }

    function seek($position)
    {
        if (!isset($this->array[$position])) {
            throw new \OutOfBoundsException("{$position} seek position 无效");
        }
        $this->position = $position;
    }

}

使用:

$test = new Test([
    'first',
    'second',
    'third'
]);

try {
    $test->seek(1);
    var_dump($test->current());
    $test->seek(3);
    var_dump($test->current());
} catch (\OutOfBoundsException $e) {
    var_dump($e->getMessage());
}
// 打印
second
3 seek position 无效

小结

至此,了解了大部分SPL所涉及到的基础接口,后面将涉及SPL提供的一系列内置类,这些类对应不同情景,极大的减少了编程任务。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 一 预定义接口 1.1 遍历 Traversable(遍历)接口 检测一个类是否可以被foreach遍历,该接口不...
    coderhu阅读 1,203评论 0 0
  • 多进程/线程 最早的服务器端程序都是通过多进程、多线程来解决并发IO的问题。进程模型出现的最早,从Unix 系统诞...
    Newt0n阅读 15,258评论 9 69
  • 课程准备知识 熟悉和了解数据结构的节本概念 熟悉PHP代码的编写 熟悉面向对象的概念 什么是 SPL SPL的基...
    followyounger1阅读 687评论 0 3
  • 很早的时候我就和一个小伙伴说过这样一段话: 每个人都有自己的缺点和优点,你喜欢她的优点,这是她吸引你的地方,也是你...
    未恋小王子阅读 365评论 0 0
  • 感恩新的一周开始了。 感恩今天邻居帮忙送大宝上学 这样宝爸只送二宝时间就不用太紧张了。 感恩昨天没有回家,又再次住...
    刘静华_2de8阅读 96评论 0 0