無標題文檔

PHP SPL,遗落的宝石

Rafael Dohms 上面的篇文章 让我惊艳了下,忍不住就翻译了下来,同时补充了部分内容。

SPL,PHP 标准库(Standard PHP Library) ,此从 PHP 5.0 起内置的组件和接口,并且从 PHP5.3 已逐渐的成熟。SPL 其实在所有的 PHP5 开发环境中被内置,同时无需任何设置。

似乎众多的 PHP 开发人员基本没有使用它,甚至闻所未闻。究其原因,可以追述到它那阳春白雪般的说明文档,使你忽略了「它的存在」。

SPL 这块宝石犹如铁达尼的「海洋之心」般,被沉入海底。而现在它应该被我们捞起,并将它穿戴在应有的位置 ,而这也是这篇文章所要表述的观点。

那么,SPL 提供了什么?

SPL 对 PHP 引擎进行了扩展,例如 ArrayAccess、Countable 和 SeekableIterator 等接口,它们用于以数组形式操作对象。同时,你还可以使用 RecursiveIterator、ArrayObejcts 等其他迭代器进行数据的迭代操作。

它还内置几个的对象例如 Exceptions、SplObserver、Spltorage 以及 spl_autoload_register、spl_classes、iterator_apply 等的帮助函数(helper functions),用于重载对应的功能。

这些工具聚合在一起就好比是把多功能的瑞士军刀,善用它们可以从质上提升 PHP 的代码效率。那么,我们如何发挥它的威力?

重载 autoloader

如果你是位「教科书式的程序员」,那么你保证了解如何使用 __autoload 去代替 includes/requires 操作惰性载入对应的类,对不?

但久之,你会发现你已经陷入了困境,首先是你要保证你的类文件必须在指定的文件路径中,例如在 Zend 框架中你必须使用「_」来分割类、方法名称(你如何解决这一问题?)。

另外的一个问题,就是当项目变得越来越复杂, __autoload 内的逻辑也会变得相应的复杂。到最后,甚至你会加入异常判断,以及将所有的载入类的逻辑如数写到其中。

大家都知道「鸡蛋不能放到一个篮子中」,利用 SPL 可以分离 __autoload 的载入逻辑。只需要写个你自己的 autoload 函数,然后利用 SPL 提供的函数重载它。

例如上述 Zend 框架的问题,你可以重载 Zend loader 对应的方法,如果它没有找到对应的类,那么就使用你先前定义的函数。

<?php
class MyLoader {
    public static function doAutoload($class) {
        // 本模块对应的 autoload 操作
    }
}

spl_autoload_register( array('MyLoader', 'doAutoload') );

正如你所见, spl_autoload_register 还能以数组的形式加入多个载入逻辑。同时,你还可以利用 spl_autoload_unregister 移除已经不再需要的载入逻辑,这功能总会用到的。

迭代器

迭代是常见设计模式之一,普遍应用于一组数据中的统一的遍历操作。可以毫不夸张的说,SPL 提供了所有你需要的对应数据类型的迭代器。

有个非常好的案例就是遍历目录。常规的做法就是使用 scandir ,然后跳过「.「 和 「..」,以及其它未满足条件的文件。例如你需要遍历个某个目录抽取其中的图片文件,就需要判断是否是 jpg、gif 结尾。

下面的代码就是使用 SPL 的迭代器执行上述递归寻找指定目录中的图片文件的例子:

<?php
class RecursiveFileFilterIterator extends FilterIterator {
    // 满足条件的扩展名
    protected $ext = array('jpg','gif');

    /**
     * 提供 $path 并生成对应的目录迭代器
     */
    public function __construct($path) {
        parent::__construct(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)));
    }

    /**
     * 检查文件扩展名是否满足条件
     */
    public function accept() {
        $item = $this->getInnerIterator();
        if ($item->isFile() && 
                in_array(pathinfo($item->getFilename(), PATHINFO_EXTENSION), $this->ext)) {
            return TRUE;
        }
    }
}

// 实例化
foreach (new RecursiveFileFilterIterator('/path/to/something') as $item) {
    echo $item . PHP_EOL;
}

你可能会说,这不是花了更多的代码去办同一件事情吗?那么,查看上面的代码,你不是拥有了具有高度重用而且可以测试的代码了吗 :^)

下面是 SPL 提供的其他的迭代器:

  • RecursiveIterator
  • RecursiveIteratorIterator
  • OuterIterator
  • IteratorIterator
  • FilterIterator
  • RecursiveFilterIterator
  • ParentIterator
  • SeekableIterator
  • LimitIterator
  • GlobIterator
  • CachingIterator
  • RecursiveCachingIterator
  • NoRewindIterator
  • AppendIterator
  • RecursiveIteratorIterator
  • InfiniteIterator
  • RegexIterator
  • RecursiveRegexIterator
  • EmptyIterator
  • RecursiveTreeIterator
  • ArrayIterator

自 PHP5.3 开始,会内置其他更多的迭代器,我想你都可以尝试下,或许它能改变你编写传统代码的习惯。

SplFixedArray

SPL 还内置了一系列的数组操作工具,例如可以使用 SplFixedArray 实例化一个固定长度的数组。那么为什么要使用它?因为它更快,甚至它关系着你的工资问题 :^)

我们知道 PHP 常规的数组包含不同类型的键,例如数字、字符串等,并且长度是可变的。正是因为这些「高级功能」,PHP 以散列(hash)的方式通过键得到对应的值 -- 其实这在特定情况这会造成性能问题。

而 SplFixedArray 因为是使用固定的数字键,所以它并没有使用散列存储方式。不确切的说,甚至你可以认为它就是个 C 数组。这就是为什么 SplFixedArray 会比通常数组要快的原因(仅在 PHP5.3 中)。

那到底有多快呢,下面的组数据可以让你窥其究竟。

https://friable.rocks/_/2009_11_05/02539798505b.jpg

更详细的评测可以参考这里 ,如果你需要大量的数组操作,那么你可以尝试下,相信它是值得信赖的。

数据结构

同时 SPL 还提供了些数据结构基本类型的实现 。虽然我们可以使用传统的变量类型来描述数据结构,例如用数组来描述堆栈(Strack)-- 然后使用对应的方式 pop 和 push(array_pop()、array_push()),但你得时刻小心,·因为毕竟它们不是专门用于描述数据结构的 -- 一次误操作就有可能破坏该堆栈。

SPL 的 SplStack 对象则严格以堆栈的形式描述数据,并提供对应的方法。同时,这样的代码应该也能理解它在操作堆栈而非某个数组,从而能让你的同伴更好的理解相应的代码,并且它更快。

最后,可能上述那些惨白的例子还不足矣「诱惑你」去使用 SPL。实践出真知,SPL 更多、更强大的功能需要你自己去挖掘。而它正如宝石般的慢慢雕砌,才能散发光辉。

PS,有关 SPL 详细的中文文档, 阮一峰同学这里有份更详细的笔记 ,推荐。

-- EOF --

那些注定的软件

最近款某来头的软件 ,似乎又撩动了互联网的神经。不想直接说我的看法,只想举两个本人亲身经历的事情。

高中时由于老师的提举,管理学校的计算机机房。某日,收到通知要全面安装「网络爸爸」这款软件(听着名字就知道干嘛用的)。当时连夜将几十台机子装上了这款软件,印象中这款软件安装非常方便 -- 甚至没有让你选择安装路径。

随后就是噩梦的开始,类似网页打不开等诸多的问题累坏了我们这帮「免费的网管」。最难以接受的就是每次启动系统时,其都会向服务器请求更新,而期间会造成机子假死。很多不明真相、而且耐不住性子的同学,顺手就直接按电源键了。

最后,我们采取的办法就是直接卸载「网络爸爸」。为了躲避老师的检查,自己用 VB 写了个「假的」系统图标。

大学时打暑期工,去帮电信局接宽带。当时,上级明令要求安装「星空极速」软件拨号上网。

开始时,我非常的「敬业」给每台机子都安装上。随着安装量的上升,问题也不断的凸显出来。陆续有用户反映弹出广告、系统拖慢等问题。老到的师哥告诉我解决办法 -- 和他们说是电脑系统的问题,让他们找装机商去。

直到某天,我自己终于厌倦每天安装「新空极速」、应付用户的投诉,这些重复劳动。突然我意识到,这软件对于我而言没有任何的好处:老大看不到,他们只拿装机的指标、那软件太大安装非常的慢,等待很痛苦很无意义。

于是,直接使用 XP 自带的 ADSL 拨号软件,几步搞定。至此以后,没有收到任何的投诉(当时最后我才知道,投诉率还和工钱挂钩),同时由于安装量上升,我比上面提到的那师哥还多拿了餐饭钱。

-- Split --

是母鸡都指望自己下个蛋,只要有这些母鸡在,就别指望上述软件的消失。而愚蠢、懒惰的生物迟早会被淘汰,这是我们所共知的自然规律,同时这一规律同样适用于其他领域。

所以,对于那些所谓软件(工具)的最后的下场,想必各位也心中自知了吧。

innerHTML 的些摘记

异步 innerHTML

innerHTML 插入节点的性能的问题,通常是我们最关注的。 在回答这问题时James Padolsey 给出了他的解决方案 ,看到上述代码不仅赞叹了下:

function asyncInnerHTML(HTML, callback) {
    var temp = document.createElement('div'),
        frag = document.createDocumentFragment();
    temp.innerHTML = HTML;
    (function(){
        if(temp.firstChild) {
            frag.appendChild(temp.firstChild);
            setTimeout(arguments.callee, 0);
        } else {
            callback(frag);
        }
    })();
}
  1. 充分利用闭包解决 IE6 的内存溢出问题
  2. 使用 延时 0 将操作从队列中拉出 ,防止浏览器假死
  3. Document Fragment 给予我们个相当好的沙盘,只是我们经常忘记了它
  4. 回调的节点可以使用 DOM 标准的手法(appendChild)插入

了解了参数就很容易调用,例如

var htmlStr = '<div><p>...</p><p>...</p><div><div>...</div>';
asyncInnerHTML(htmlStr, function(fragment){
    document.body.appendChild(fragment);
});

再次不禁赞叹下!

组织 innerHTML 字符串

说到 innerHTML ,通常在这操作之前会有大部分的字符串操作用于连接节点。考虑下面的三种做法,有何不同

方式一

var arr = ['item 1', 'item 2', 'item 3', ...];
for (var i = 0, l = arr.length, list = ''; i < l; i++) {
    list += '<li>' + arr[i] + '</li>';
}
list = '<ul>' + list + '</ul>';

方式二

var arr = ['item 1', 'item 2', 'item 3', ...];
for (var i = 0, l = arr.length, list = []; i < l; i++) {
    list[list.length] = '<li>' + arr[i] + '</li>';
}
list = '<ul>' + list.join('') + '</ul>';

方式三

var arr = ['item 1', 'item 2', 'item 3', ...];
var list = '<ul><li>' + arr.join('</li><li>') + '</li></ul>';

详细的对比 测试在这里 (没错,还是 James Padolsey 那小子的 Blog)。同时, PPK 也整理了份有关 innerHTML 的速度测试报告

IE 的陷阱

对于 IE,innerHTML 有个不大不小的陷阱( via ),就是在 tbody 中插入 innerHTML 时,会报莫名的「未知的运行错误」。

测试地址在这里 (经过测试,在 IE8 中仍然如此)。有兴趣的同学可以 参看更详细的信息

我的照片

嗨!我叫「明城」,八零后、码农、宁波佬,现居杭州。除了这里,同时也欢迎您关注我的 GitHubTwitterInstagram 等。

这个 Blog 原先的名字叫 Gracecode.com 、现在叫 「無標題文檔」 。 要知道作为码农取名是件很难的事情,所以不想在取名这事情上太费心思。

作为八零后,自认为还仅存点点可能不怎么被理解的幽默感,以及对平淡生活的追求和向往。 为了避免不必要的麻烦,声明本站所输出的内容以及观点仅代表个人,不代表自己所服务公司或组织的任何立场。

如果您想联系我,可以发我邮件 `echo bWluZ2NoZW5nQG91dGxvb2suY29tCg== | base64 -d`

分类

搜索

文章