swimlanescape

2009年03月6日

窥探jQuery——面向JavaScript程序员

类归于: JavaScript — admin @ 9:16 上午

作者:Simon Willison

原文:http://simonwillison.net/2007/Aug/15/jquery/

jQuery 在2006年1月现身时,给我的第一印象,是这玩意儿构造得很精明。基于CSS选择器(CSS selectors)来打点一切,其思路相当灵巧(参考getElementsBySelector)。但链盒工事(chaining stuff)看起来更像个噱头,并且整体看来,jQuery库提供的功能并不能覆盖所有基础性的东西。因此我断定,jQuery只会昙花一现。

几 个月以来,我逐渐明白自己想错了。从技术工艺上考量,jQuery十分凌厉。它用简洁的方法,把大量常用功能封装起来,并提供精巧的插入式API,来满足 标准库之外的功能模块的实现。jQuery秉持的核心,乃DOM元素的集合(译注:通常是某些子集合)——它把元素集合作为一个根本,给高度抽象出来了。 最重要的,是这种遵循最佳实践的抽象,能让jQuery与其他JavaScript代码相处融洽。

很多对jQuery的介绍,都是针对设计师和初级开发人员。接下来我想说明,为什么jQuery也会吸引那些富有经验的开发人员。

名称空间(Namespacing)

编写可重用的、优秀的JavaScript代码,其关键在于对名称空间的积极把控。JavaScript只拥有单一的、全局的名称空间(即window对象),而很多程序员(以及一些库)恣意地为之添加各种东西。要知道全局变量是魔鬼!聪明的开发人员,会使用类似组件模式的技术,来尽力减少全局对象的数量。

jQuery仅向全局名称空间引入一个标记:jQuery函数/对象。其余的要么是jQuery的直接属性(译注:原文‘directy property’系笔误,应是‘direct property’),要么就是调用jQuery函数所返回的对象的方法。

那 “语言升级”(language enhancements)又是什么呢?大多数库会提供映射,过滤,剥离,往往是浏览器的JavaScript引擎所缺少的那些功能。还有一些库,直接扩 展了JavaScript内置的String和Array类,但这是冒险的做法。String.prototype和Array.prototype也有 各自的名称空间,在其内添加的属性一旦发生冲突,所带来的风险,不亚于在全局环境下的草率大意。

在语言升级方面,jQuery提供了很多函数(功能),但每个函数都被赋给jQuery对象的属性:jQuery.each,jQuery.extend,jQuery.grep,jQuery.map,jQuery.merge以及jQuery.trim。如此一来,它们就不会跟其他代码产生冲突。

声名狼藉的$函数(The infamous $ function)

刚才我说到,jQuery是唯一被引入的全局标记,其实并不尽然:$标记作为jQuery的快捷方式,也被引入进来。庆幸的是,$的存在不会带来负面影响:如果你需要让原始的$起死回生(比如,这之前你的代码使用了Prototype),你可以调用jQuery.noConflict()来恢复它。

如果你既想拥有$的便利,又不希望jQuery跟其他同样使用了全局$函数的代码发生冲突,可遵循jQuery文档所建议的惯用方式:

(function($) {
// 在这个函数体里,$可作为jQuery的引用
// 很方便,对吧?
})(jQuery);

把一切都附加到$标记的做法,曾让我认为jQuery华而不实。不过,从体系的角度来审视这种设计,一切又是非常明了的——尽管我常喜欢在代码中定义自己的$快捷方式。

选取元素(Selecting some elements)

jQuery的每个操作,都以选取DOM中一个或更多的节点(nodes)作为开始。jQuery(拥有一种真正的面向特定领域)的选取语法,是十分有趣的,它结合了CSS 1,CSS 2,部分CSS 3语法,一些XPath语法,以及一些特定的扩展。在这里我不会做详细介绍,我只列出几个有用的例子:

jQuery('div.panel')
选取了所有class=”panel”的div
jQuery('p#intro')
选取了所有id=”intro”的段落
jQuery('div#content a:visible')
选取了id=”content”的div中所有可见的链接
jQuery('input[@name=email]‘)
选取了所有name=”email”的输入域
jQuery('table.orders tr:odd')
选取了类名为“orders”的表中所有的奇数行
jQuery('a[@href^="http://"]‘)
选取了所有(以http://开头的)外部链接
jQuery('p[a]‘)
选取了所有包含一个或多个链接的段落

上述例子中,:visible和:odd是jQuery实现的扩展,很具特色。而属性的选取使用@作为标记,其方式和XPath一样,要优于CSS 2。


jQuery的这套选取语法包罗万象,有些类似正则表达式,想完全消化是需要花上一段时间的。

把玩一下(Doing stuff with them)

通过jQuery的选取操作,我们能得到一些很棒的“素材”(beast)。它们是一个集合,包含了DOM元素,并且类似数组那样,拥有length属性;通过索引可以访问集合中的元素。在Firebug console的交互模式下,集合也被显示成一个数组,这个特性非常有用。集合实际上是一个jQuery对象,这个对象被赋予了很多方法(methods),用来查询,修改,扩展集合中的元素。

jQuery的方法(methods),本质上可分成三种:一种可以操作那些符合匹配的元素;一种可以返回第一个匹配到的对象的值;一种可以变更被选取的集合。

我不会列出所有的方法(可参考visualjquery.com),但我用例子做一下说明。如果你的浏览器装了Firebug,你可以以交互方式运行这些示例代码:首先使用这个bookmarklet(译注[1])把jQuery库载入至浏览器的任意页面,然后把示例代码粘贴到Firebug console中。

jQuery('div#primary').width(300);
把id=”primary”的div的宽度设为300px
jQuery('p').css('line-height', '1.8em');
把所有段落的line-height设为1.8em
jQuery('li:odd').css({color: 'white', backgroundColor: 'black'});
向间隔的list项添加两个CSS规则;注意css()函数可以用一个对象来代替两个字符串作为参数
jQuery('a[@href^="http://"]‘).addClass(’external’).attr(’target’, ‘_blank’);
向所有(以http://开头的)外部链接添加“external”类,然后策略性地加上target=”_blank”属性。这个示例用到了链盒(chaining),稍后会做介绍。
jQuery('blockquote').each(function(el) { alert(jQuery(this).text()) });
遍历页面上的每个<blockquote>,并显示出它的文字内容(包括HTML标签)
jQuery('a').html('Click here!');
用阴险的“Click here!”代替页面上所有的链接<a>的文字

下面的示例展示了jQuery如何取得第一个匹配到的对象的值:

var width = jQuery('div').width();
页面上第一个div的宽度
var src = jQuery('img').attr('src');
页面上第一张图片的src属性值
var color = jQuery('h1').css('color');
第一个<h1>的颜色样式值

在 jQuery的方法构造中,蕴含着令人惬意的对称性:当向方法传递两个参数或一个对象时,方法可被用来执行设置操作;如果只向方法传递一个参数,则可以让 它执行取值操作(译注:读者可对照上面的示例代码感受一下)。这种对称性设计贯穿了jQuery体系,使得API的文法更容易被记忆。

本节最后的例子,展示了一些可变更被选取的元素集合的方法。这些方法大多都提高了检索DOM的简易程度:

jQuery('div').not('[@id]‘)
返回那些没有id属性的div
jQuery('h2').parent()
返回那些是<h2>的直接父节点元素
jQuery('blockquote').children()
返回所有<blockquote>的子节点元素
jQuery('p').eq(4).next()
在页面上找到第五个段落(译注:因为集合的元素索引从0开始),然后根据节点的树层结构关系,找到并返回这个段落节点右侧的兄弟节点元素
jQuery('input:text:first').parents('form')
找到并返回页面上第一个type=”text”的输入域所在的form节点元素,parents()的可选参数是另一个选择器

链盒(Chaining)

jQuery 开发团队经常夸耀jQuery的链盒理念(译注[2]),甚至在网站首页上宣扬“jQuery将改变你编写JavaScript的方式”。我个人感觉,这 么做多少有点误导大众,我愿意告诉大家,你完全可以取jQuery之长,却应避免冗长的方法链盒(chains of methods)。

也 就是说,链盒有时会像变戏法一样。除了使用链盒将各种操作DOM的方法粘到一起,你也可以使用jQuery的end()方法,来实现在特定范围内推进或回 溯你需要得到的元素。这个概念很难解释清楚。本质上讲,每次使用(诸如children()或filter())方法来改变元素集合时,你可以在这些方法 之后使用end(),来重新定位你最初选取的元素集合。关于这点,Jesse Skinner在他的Simplify Ajax development with jQuery(译注[3])教程中给出了实例:

$('form#login')
// 第一步,隐藏表单中那些带有'optional'类的<label>
.find('label.optional').hide().end()
// 第二步,为表单的密码输入域渲染上红色边框
.find('input:password').css('border', '1px solid red').end()
// 第三步,为表单加上提交处理
.submit(function(){
return confirm('Are you sure you want to submit?');
});

这个示例读起来就像句俏皮话。整个过程是,先选取一个表单,再在其中选取一些元素做修改,然后回溯到表单,为它定义一个submit()处理。


示例很酷,但如果你不习惯,也可以不这么用。我就很乐意用自定义变量来规划代码。


操作DOM(DOM Manipulation)

jQuery提供了几个大规模操作DOM的卓越方法。第一种非常让人惊叹:jQuery()函数能把HTML片段插入DOM元素中(实际上,函数会留意以’<’打头的字符串参数):

var div = jQuery('<div>Some text</div>');

一旦你创建好了div,便可以继续用链盒向其添加属性:

var div = jQuery('<div>Some text</div>').addClass('inserted').attr('id', 'foo');

现在把div加到body上:

div.appendTo(document.body)

或用选择器把div加到已知元素的前面:

div.prependTo('div#primary')

处理事件(Handling events)

任 何JavaScript库都需要事件处理能力,jQuery也不例外。类似attr()和css()的行为,各种与事件处理相关的方法也有双重用途:一种 是把函数当作参数,赋给事件处理器;一种是不带参数,可以模拟事件被触发(译注:前提是事件已经定义,可参考visualjquery.com > Events > click()):

jQuery('p').click(function() { jQuery(this).css('background-color', 'red'); });
为所有段落增加点击事件,当你点击它们时,段落背景会变成红色
jQuery('p:first').click()
然后在第一个段落上模拟点击的动作,它的背景会变成红色

类似的函数还包括mouseover,keyup等,对应着浏览器通常支持的那些动作。留意一下事件处理中的’this’关键字,它代表触发事件的元素;jQuery(this)是一种惯用语法,可以让this所代表的元素应用各种jQuery方法。

这里有两个与事件相关的函数值得仔细说一下:

jQuery('a').hover(function() {
jQuery(this).css('background-color', 'orange');
}, function() {
jQuery(this).css('background-color', 'white');
});

hover()可设定两个函数,分别对应onmouseover和onmouseout事件。

jQuery('p').one('click', function() { alert(jQuery(this).html()); });

one()设定的事件在第一次被触发后便被移除。上面的示例会让所有段落在第一次被点击时显示其文字内容。

凭借bind()和trigger()方法,jQuery也可以支持自定义事件(click()家族仅仅是便捷方法,只支持有限的事件)。自定义事件可接受参数,trigger()可接受数组作为参数,来做各种处理操作:

jQuery(document).bind('stuffHappened', function(event, msg) {
alert('stuff happened: ' + msg);
});
jQuery(document).trigger('stuffHappened', ['Hello!']);

渐进式编码Unobtrusive scripting

本小节的标题很令我钟意。我一直认为,最好的Web应用程序,往往是那些在脚本被禁用后仍能正常使用的程序。想建立这样的应用程序,最好的方法就是遵循渐进式编码,让普通页面完全加载后,再为页面中的元素赋以事件处理(更多信息可参考渐进式编码Hijax)。

jQuery对这种编码策略提供了绝好支持。首先,从整体上看,节点选取暗合jQuery以及渐进式编码的核心理念。其次,针对window.onload问题,jQuery提供了一套解决方案,这套方案借鉴了Dean Edward的成果,使得以“DOM加载完毕”为信号的事件能跨浏览器工作。你可以在浏览器完全加载DOM后设定并运行一个函数,如下所示:

jQuery(document).ready(function() {
alert('The DOM is ready!');
});

你甚至可以直接传递一个函数给jQuery(),以更简洁的方式达到同样效果:

jQuery(function() {
alert('The DOM is ready!');
});

jQuery与Ajax(jQuery and Ajax)

在我所知道的主流JavaScript库中,jQuery拥有最棒的Ajax API。最简单的Ajax调用如:

jQuery('div#intro').load('/some/fragment.html');

代码以GET请求方式,从/some/fragment.html文件中获取HTML片段,并把片段装载到id=”intro”的div中。

当 我第一次看到这行代码时,几乎对它没什么印象。这看起来非常简洁,但如果你想用jQuery做些更复杂的事情,比如显示Ajax装载进度,该如何做 呢?jQuery为你准备了一些可自定义的事件(ajaxStart,ajaxComplete,ajaxError等等),来实现你想要的代码。同时 jQuery也提供了广泛的底层API,来实现更复杂的Ajax交互:

jQuery.get('/some/script.php', {'name': 'Simon'}, function(data) {
alert('The server said: ' + data);
}); // 以GET方式通过/some/script.php?name=Simon获取数据

jQuery.post('/some/script.php', {'name': 'Simon'}, function(data) {
alert('The server said: ' + data);
}); // 以POST方式向/some/script.php发送请求

jQuery.getJSON('/some.json', function(json) {
alert('JSON rocks: ' + json.foo + ' ' + json.bar);
}); // 从/some.json接收并解析数据,把数据转换成JSON格式

jQuery.getScript('/script.js'); // 以GET方式获取/script.js脚本并用eval()执行

插件(Plugins)

就你所能获得的功能的数量而言,jQuery库其实是相当小的——对代码做紧凑处理后只有20KB左右,甚至用gzip压缩后会变得更小。向标准库添加额外功能时,需用插件的方式来做,它可以(也确实能够)向现有的jQuery实例对象添加全新的方法。如果你想执行:

jQuery('p').bounceAroundTheScreenAndTurnGreen();

jQuery的插件机制提供了文档说明型的挂载方式(documented hooks),可以实现把上述方法添加到jQuery中。这种简易的创建形式,吸引了很多插件作者,他们让人印象深刻;现在插件目录中已经有超过100个插件了。

真正绝妙的,是你可以像自定义方法那样,来定义选择器。比如,moreSelectors插件实现了诸如“div:color(red)”的方法,来匹配包含红色文本的div。

并非天衣无缝Leaky abstractions

在 发掘jQuery各种特性的同时,我也被某个我视之为教条(philosophical blocker)的东西所折磨着。几年来,我总是建议大家使用一种JavaScript库,前提是你们愿意梳理它的源码,并把它的工作原理彻底搞懂。我发 出如此论调,是基于Joel Spolsky的不健全抽象的法则(译注[4])。在那篇文章中,Joel指出,API把复杂性隐藏的越多,当它出现无法应付的意外时,你越有可能遭遇更多的麻烦。浏览器平台是不健全抽象的最佳代表,所以当库无法帮你摆脱困境时,你要自寻解药。保持警觉非常重要。

jQuery 使用了相当不可思议的技术,以求实现它所设想的各种功能——其中一些(比如选择器的代码)真是震天骇地。如果有必要彻底搞懂一个库的工作原理,那么对大多 数开发人员来说,jQuery不会是上佳之选。然而,jQuery拥有极高的人气,并且没有太多与之相关的恐怖经典流于街巷(译注:原文是a distinct lack of horror stories,比如微软Win95的“蓝屏”就是恐怖经典:),所以具体到jQuery所用技艺的邪正之分,也就变得不那么重要了。

我 想,我必须重新审视曾给大家的建议。库的运作机制并不是问题焦点:关键是应看清更具普遍性的潜在问题,知晓浏览器之间的差别,以及你使用库的哪种技术,来 消除差别造成的负面影响。没有哪种库可以一劳永逸地帮你克服浏览器的古怪行为。但只要你对应付潜在问题训练有素,便可把握经脉,指出问题的源头——无论它 们来自你自己编写的代码,还是库或者应付策略本身。

结语(To conclude)

我费了那么多口舌,希望能让大家明白,jQuery不只是又一个JavaScript库那么简单——它蕴含了很多值得品味揣摩的理念,甚至能启迪那些骨灰级的JavaScript程序员。如果你不打算尝试jQuery,但仍值得去花些时间探索一下jQuery的生态体系(the jQuery ecosystem)。

Simon Willison写于2007年8月15日 凌晨2:27

译注:

[1] bookmarklet在原文中指的是一段“Insert jQuery”的JavaScript代码,由于译者使用Google Docs进行在线翻译,链接中的JS代码被编辑器屏蔽掉了,下面列出的代码可粘贴到浏览器的地址栏中执行,执行后才可以继续用示例代码查看jQuery的 选取效果:
javascript:void(function(){var s=document.createElement(’script’);s.src=’http://code.jquery.com/jquery-1.1.2.js’;document.getElementsByTagName(’head’)[0].appendChild(s);}())

[2] 本文使用的术语“链盒”,大抵可对应单词chain/chaining/chainable;译者在参考jQuery Magazine issue 1对 jQuery选择器运行方式的图解后,确定了这种译法。汉字是象形文字,按“盒”字的构造来体会jQuery颇有意趣:比如上面的“人”字,不正是选择器 “吐出”特定元素的“嘴”吗?而“人”字下面“一”“口”“皿”的逐层累积,其形状又类似jQuery的Logo,并让人联想到链式选取过程中不同的元素 集合;“盒”即是“桶”(bucket),译者自以为绝妙!

[3] IBM developerWorks中国的官方翻译版本《使用jQuery简化Ajax开发》;译言版本分12两部分,由令狐葱翻译。

[4] Law of Leaky Abstractions: All non-trivial abstractions, to some degree, are leaky. 详细解读请见原文

CASE 竞价通2.0 PHP代码优化

类归于: PHP — admin @ 8:59 上午

主题
缩减竞价通2.0前台首页加载时间(PHP代码优化)。

关键词
PHP,竞价通2.0,优化

分类
Web开发

提供者
电子商务部,杨洋<yangyang@myce.net.cn>

问题描述
竞价通2.0平台,前台首页加载时间过长。主要由PHP代码执行,SQL查询,以及业务UI设计上的问题导致。我们已实施了一些缩减首页加载时间的解决方案,本CASE陈述PHP代码优化方面。

解决方法
根据个人经验,翻译国外技术文献精华并按其实践,是得到解决问题途径的好方法之一。在遇到首页问题之前,我已翻译了优化PHP代码的40条建议一文。随后根据翻译所得,对涉及首页的PHP代码做了一些优化。比如用strtr替代str_replace函数(参见译文中第11条)来转化a标签中title属性含有的特殊字符。示例代码如下:

$str
.= "<a href=\"{$rowstr[$i][0]}\" title=\"" . strtr($rowstr[$i][1],
array('>'=>'&gt', '<'=>'&lt')) .
"\">{$rowstr[$i][2]}</a>";


在只对PHP代码做优化处理后,根据测试人员测试,首页登录时间缩减了约2秒。

改进方案
1. 可以继续优化SQL查询,重新布局业务UI,只在首页展示对用户最重要的数据。

避免此类问题的建议
1. 尽可能做页面静态化处理。
2. 尽可能在各方面做优化处理。

附译文:
优化PHP代码的40条建议
原文地址:http://reinholdweber.com/?p=3
原文作者:Reinhold Weber
英文版权归Reinhold Weber所有,中译文作者yangyang(aka davidkoree)。双语版可用于非商业传播,但须注明英文版作者、版权信息,以及中译文作者。翻译水平有限,请广大PHPer指正。

1.    If a method can be static, declare it static. Speed improvement is by a factor of 4. 如果一个方法可静态化,就对它做静态声明。速率可提升至4倍。

2.    echo is faster than print. echoprint 快。

3.    Use echo’s multiple parameters instead of string concatenation. 使用echo的多重参数(译注:指用逗号而不是句点)代替字符串连接。

4.    Set the maxvalue for your for-loops before and not in the loop. 在执行for循环之前确定最大循环数,不要每循环一次都计算最大值。

5.    Unset your variables to free memory, especially large arrays. 注销那些不用的变量尤其是大数组,以便释放内存。

6.    Avoid magic like __get, __set, __autoload 尽量避免使用__get,__set,__autoload。

7.    require_once() is expensive require_once()代价昂贵。

8.    Use full paths in includes and requires, less time spent on resolving the OS paths. 在包含文件时使用完整路径,解析操作系统路径所需的时间会更少。

9.    If you need to find out the time when the script started executing, $_SERVER[’REQUEST_TIME’] is preferred to time() 如果你想知道脚本开始执行(译注:即服务器端收到客户端请求)的时刻,使用$_SERVER[‘REQUEST_TIME’]要好于time()。

10.    See if you can use strncasecmp, strpbrk and stripos instead of regex. 检查是否能用strncasecmp,strpbrk,stripos函数代替正则表达式完成相同功能。

11.    str_replace is faster than preg_replace, but strtr is faster than str_replace by a factor of 4. str_replace函数比preg_replace函数快,但strtr函数的效率是str_replace函数的四倍。

12.    If the function, such as string replacement function, accepts both arrays and single characters as arguments, and if your argument list is not too long, consider writing a few redundant replacement statements, passing one character at a time, instead of one line of code that accepts arrays as search and replace arguments. 如果一个字符串替换函数,可接受数组或字符作为参数,并且参数长度不太长,那么可以考虑额外写一段替换代码,使得每次传递参数是一个字符,而不是只写一行 代码接受数组作为查询和替换的参数。

13.    It’s better to use select statements than multi if, else if, statements. 使用选择分支语句(译注:即switch case)好于使用多个if,else if语句。

14.    Error suppression with @ is very slow. 用@屏蔽错误消息的做法非常低效。

15.    Turn on apache’s mod_deflate 打开apache的mod_deflate模块。

16.    Close your database connections when you’re done with them. 数据库连接当使用完毕时应关掉。

17.    $row[’id’] is 7 times faster than $row[id]. $row[‘id’]的效率是$row[id]的7倍。

18.    Error messages are expensive. 错误消息代价昂贵。

19.    Do not use functions inside of for loop, such as for ($x=0; $x < count($array); $x) The count() function gets called each time. 尽量不要在for循环中使用函数,比如for ($x=0; $x < count($array); $x)每循环一次都会调用count()函数。

20.    Incrementing a local variable in a method is the fastest. Nearly the same as calling a local variable in a function. 在方法中递增局部变量,速度是最快的。几乎与在函数中调用局部变量的速度相当。

21.    Incrementing a global variable is 2 times slow than a local var. 递增一个全局变量要比递增一个局部变量慢2倍。

22.    Incrementing an object property (eg. $this->prop++) is 3 times slower than a local variable. 递增一个对象属性(如:$this->prop++)要比递增一个局部变量慢3倍。

23.    Incrementing an undefined local variable is 9-10 times slower than a pre-initialized one. 递增一个未预定义的局部变量要比递增一个预定义的局部变量慢9至10倍。

24.    Just declaring a global variable without using it in a function also slows things down (by about the same amount as incrementing a local var). PHP probably does a check to see if the global exists. 仅定义一个局部变量而没在函数中调用它,同样会减慢速度(其程度相当于递增一个局部变量)。PHP大概会检查看是否存在全局变量。

25.    Method invocation appears to be independent of the number of methods defined in the class because I added 10 more methods to the test class (before and after the test method) with no change in performance. 方法调用看来与类中定义的方法的数量无关,因为我(在测试方法之前和之后都)添加了10个方法,但性能上没有变化。

26.    Methods in derived classes run faster than ones defined in the base class. 派生类中的方法运行起来要快于在基类中定义的同样的方法。

27.    A function call with one parameter and an empty function body takes about the same time as doing 7-8 $localvar++ operations. A similar method call is of course about 15 $localvar++ operations. 调用带有一个参数的空函数,其花费的时间相当于执行7至8次的局部变量递增操作。类似的方法调用所花费的时间接近于15次的局部变量递增操作。

28.    Surrounding your string by ‘ instead of ” will make things interpret a little faster since php looks for variables inside “…” but not inside ‘…’. Of course you can only do this when you don’t need to have variables in the string. 用单引号代替双引号来包含字符串,这样做会更快一些。因为PHP会在双引号包围的字符串中搜寻变量,单引号则不会。当然,只有当你不需要在字符串中包含变 量时才可以这么做。

29.    When echoing strings it’s faster to separate them by comma instead of dot. Note: This only works with echo, which is a function that can take several strings as arguments. 输出多个字符串时,用逗号代替句点来分隔字符串,速度更快。注意:只有echo能这么做,它是一种可以把多个字符串当作参数的“函数”(译注:PHP手册 中说echo是语言结构,不是真正的函数,故把函数加上了双引号)。

30.    A PHP script will be served at least 2-10 times slower than a static HTML page by Apache. Try to use more static HTML pages and fewer scripts. Apache解析一个PHP脚本的时间要比解析一个静态HTML页面慢2至10倍。尽量多用静态HTML页面,少用脚本。

31.    Your PHP scripts are recompiled every time unless the scripts are cached. Install a PHP caching product to typically increase performance by 25-100% by removing compile times. 除非脚本可以缓存,否则每次调用时都会重新编译一次。引入一套PHP缓存机制通常可以提升25%至100%的性能,以免除编译开销。

32.    Cache as much as possible. Use memcached - memcached is a high-performance memory object caching system intended to speed up dynamic web applications by alleviating database load. OP code caches are useful so that your script does not have to be compiled on every request. 尽量做缓存,可使用memcached。memcached是一款高性能的内存对象缓存系统,可用来加速动态Web应用程序,减轻数据库负载。对运算码 (OP code)的缓存很有用,使得脚本不必为每个请求做重新编译。

33.    When working with strings and you need to check that the string is either of a certain length you’d understandably would want to use the strlen() function. This function is pretty quick since it’s operation does not perform any calculation but merely return the already known length of a string available in the zval structure (internal C struct used to store variables in PHP). However because strlen() is a function it is still somewhat slow because the function call requires several operations such as lowercase & hashtable lookup followed by the execution of said function. In some instance you can improve the speed of your code by using an isset() trick. 当操作字符串并需要检验其长度是否满足某种要求时,你想当然地会使用strlen()函数。此函数执行起来相当快,因为它不做任何计算,只返回在zval 结构(C的内置数据结构,用于存储PHP变量)中存储的已知字符串长度。但是,由于strlen()是函数,多多少少会有些慢,因为函数调用会经过诸多步 骤,如字母小写化(译注:指函数名小写化,PHP不区分函数名大小写)、哈希查找,会跟随被调用的函数一起执行。在某些情况下,你可以使用isset() 技巧加速执行你的代码。

Ex.(举例如下)
if (strlen($foo) < 5) { echo “Foo is too short”; }
vs.(与下面的技巧做比较)
if (!isset($foo{5})) { echo “Foo is too short”; }

Calling isset() happens to be faster then strlen() because unlike strlen(), isset() is a language construct and not a function meaning that it’s execution does not require function lookups and lowercase. This means you have virtually no overhead on top of the actual code that determines the string’s length. 调用isset()恰巧比strlen()快,因为与后者不同的是,isset()作为一种语言结构,意味着它的执行不需要函数查找和字母小写化。也就是 说,实际上在检验字符串长度的顶层代码中你没有花太多开销。

34.    When incrementing or decrementing the value of the variable $i++ happens to be a tad slower then ++$i. This is something PHP specific and does not apply to other languages, so don’t go modifying your C or Java code thinking it’ll suddenly become faster, it won’t. ++$i happens to be faster in PHP because instead of 4 opcodes used for $i++ you only need 3. Post incrementation actually causes in the creation of a temporary var that is then incremented. While pre-incrementation increases the original value directly. This is one of the optimization that opcode optimized like Zend’s PHP optimizer. It is still a good idea to keep in mind since not all opcode optimizers perform this optimization and there are plenty of ISPs and servers running without an opcode optimizer. 当执行变量$i的递增或递减时,$i++会比++$i慢一些。这种差异是PHP特有的,并不适用于其他语言,所以请不要修改你的C或Java代码并指望它 们能立即变快,没用的。++$i更快是因为它只需要3条指令(opcodes),$i++则需要4条指令。后置递增实际上会产生一个临时变量,这个临时变 量随后被递增。而前置递增直接在原值上递增。这是最优化处理的一种,正如Zend的PHP优化器所作的那样。牢记这个优化处理不失为一个好主意,因为并不 是所有的指令优化器都会做同样的优化处理,并且存在大量没有装配指令优化器的互联网服务提供商(ISPs)和服务器。

35.    Not everything has to be OOP, often it is too much overhead, each method and object call consumes a lot of memory. 并不是事必面向对象(OOP),面向对象往往开销很大,每个方法和对象调用都会消耗很多内存。

36.    Do not implement every data structure as a class, arrays are useful, too. 并非要用类实现所有的数据结构,数组也很有用。

37.    Don’t split methods too much, think, which code you will really re-use. 不要把方法细分得过多,仔细想想你真正打算重用的是哪些代码?

38.    You can always split the code of a method later, when needed. 当你需要时,你总能把代码分解成方法。

39.    Make use of the countless predefined functions. 尽量采用大量的PHP内置函数。

40.    If you have very time consuming functions in your code, consider writing them as C extensions. 如果在代码中存在大量耗时的函数,你可以考虑用C扩展的方式实现它们。

41.    Profile your code. A profiler shows you, which parts of your code consumes how many time. The Xdebug debugger already contains a profiler. Profiling shows you the bottlenecks in overview. 评估检验(profile)你的代码。检验器会告诉你,代码的哪些部分消耗了多少时间。Xdebug调试器包含了检验程序,评估检验总体上可以显示出代码 的瓶颈。

42.    mod_gzip which is available as an Apache module compresses your data on the fly and can reduce the data to transfer up to 80%. mod_zip可作为Apache模块,用来即时压缩你的数据,并可让数据传输量降低80%。

43.    Excellent Articlehttp://phplens.com/lens/php-book/optimizing-debugging-php.php)about optimizing php by John Lim 另一篇优化PHP的精彩文章,由John Lim撰写。

CASE 竞价通2.0 基于CSS样式表实现数据条

类归于: CSS — admin @ 8:56 上午

主题
基于CSS样式表实现数据条

关键词
CSS,竞价通2.0,DHTML,Google,AdWords

分类
Web开发,Web设计

提供者
电子商务部,杨洋<yangyang@myce.net.cn>

问题描述
在 竞价通2.0开发阶段,我负责过Google AdWords中与“关键字工具”相关的程序移植与接口实现。其主要功能是,根据用户提供的关键字,返回与之相关的关键字信息,包括它们的“上月搜索量” 和“广告客户竞争程度”等数据。其中一些数据以数据条的形式展现,并且在HTML页面中以列表形式存在,用户可对之进行动态排序。Google自有平台的 JavaScript代码过于繁杂,即使想完全照搬也需消耗很多时间。并且在测试中,我发现:1 在IE下,用JavaScript结合HTML DOM对列表做排序,如果数据条以图片方式产生,则它们会被重新载入一次,但又不可能要求每位客户端用户对浏览器做优化设置。2 Google自有平台对列表做排序时,会存在部分数据条与先前不符的情况,比如某一关键字的“上月搜索量”应是固定值,经过重新排序,值偶尔会发生偏差。 但主要问题还是集中在 1 上。

解决方法
根据从 《Bulletproof Web Design》一书学到的页面设计经验,我决定用CSS(级联样式表)实现与Google相同的效果,在IE和Firefox下均显示良好。并且CSS实 现的数据条列表不存在排序重载数据的问题,解决了用户体验上的不便。代码如下:

<style type="text/css">
/* 此样式表用于关键词列表(包括上月搜索量和广告客户竞争程度数据条)的显示 */
.list {
    width:450px;
}
.head {
    list-style:none;
    float:top left;
    clear:both;
    height:24px; /* 当浮动为top left时,必须设定每行高度以使背景可以显示 */
    width:auto;
    padding:0;
    margin:0;
    background:#ffffff;
    border-bottom:2px solid #999999;
    color:#0000cc;
    font-size:13px;
    font-weight:bold;
}
.head_k {
    float:left;
    width:150px;
    text-decoration:underline;
    cursor:pointer;
}
.head_v {
    float:left;
    width:105px;
    text-decoration:underline;
    cursor:pointer;
}
.head_s {
    float:left;
    width:125px;
    text-decoration:underline;
    cursor:pointer;
}
.head_t {
    float:right;
    width:70px;
}
.add_method {
    float:right;
}
.row {
    list-style:none;
    float:top left;
    clear:both;
    height:24px; /* 当浮动为top left时,必须设定每行高度以使背景可以显示 */
    width:auto;
    padding-top:3px;
    margin:0;
    background:#ffffff;
    border-bottom:1px solid #efefef;
}
.keyword {
    float:left;
    width:150px;
    font-size:12px;
}
.keyworddel {
    float:left;
    width:150px;
    color:#999999;
    font-size:12px;
}
.volum {
    float:left;
    width:50px;
    height:10px;
    margin-right:55px;
    border:1px solid #999999;
}
.scale {
    float:left;
    width:50px;
    height:10px;
    margin-right:75px;
    border:1px solid #999999;
}
span.bar0 {
    float:left;
    width:1px;
    height:10px;
    background-color:#a4d0bb;
}
span.bar1 {
    float:left;
    width:10px;
    height:10px;
    background-color:#a4d0bb;
}
span.bar2 {
    float:left;
    width:20px;
    height:10px;
    background-color:#a4d0bb;
}
span.bar3 {
    float:left;
    width:30px;
    height:10px;
    background-color:#a4d0bb;
}
span.bar4 {
    float:left;
    width:40px;
    height:10px;
    background-color:#a4d0bb;
}
span.bar5 {
    float:left;
    width:50px;
    height:10px;
    background-color:#a4d0bb;
}
.addkwdbroad, .addkwdphrase, .addkwdexact {
    float:right;
    width:70px;
    text-align:right;
    text-decoration:underline;
    color:#0000ff;
    font-size:12px;
    cursor:pointer;
}
.delkwdbroad, .delkwdphrase, .delkwdexact {
    float:right;
    width:70px;
    text-align:right;
    text-decoration:underline;
    color:#999999;
    font-size:12px;
    cursor:pointer;
}
.kb_broad, .kb_phrase, .kb_exact {
    list-style:none;
    float:top left;
    clear:both;
    height:12px; /* 当浮动为top left时,必须设定每行高度以使背景可以显示 */
    width:100%;
    padding-top:3px;
    margin:0;
    background:#ffffff;
    border-bottom:1px solid #efefef;
}
.kb_txt {
    float:left;
    width:auto;
    font-size:12px;
    color:#000000;
}
.kb_opr {
    float:right;
    width:auto;
    font-size:12px;
    text-decoration:underline;
    color:#999999;
    cursor:pointer;
}
</style>


上述样式中的核心设计思路同样应用到了后来搜狐的“相关关键词”动态列表上,并且还借助CSS实现了翻页效果,较之搜狐自有平台的实现更neat。

改进方案
1. 数据条的宽度可以再做细微调整,以满足测试人员或用户的要求。

避免此类问题的建议
1. 对类似的夸浏览器支持方案做深度分析与总结,形成知识库。

CASE 竞价通2.0 JavaScript数值校验

类归于: JavaScript — admin @ 8:48 上午

主题
根据业务规则,用JavaScript的原型扩展方法,实现一套较为完整的、可扩展的数值校验包,用以验证客户端用户输入数值的合法性,并间接增强人机交互过程中的可亲度。

关键词
JavaScript,竞价通2.0,数值校验,prototype,正则表达式

分类
Web开发

提供者
电子商务部,杨洋<yangyang@myce.net.cn>

问题描述
在 竞价通2.0平台,业务特性决定了客户端的人机交互过程中要大规模应用JavaScript,包括页面逻辑跳转,列表元素动态渲染,用户输入验证,所见即 所得(WYSIWYG)效果等,其中数值验证占了很大比例。除去通常需要处理的几种数值约束(非负非零、小数点保留两位、非科学计数),客户在各门户广告 发布管理页面输入的数值也受门户特定规则制约。比如TOM关键词的最小递增价格为0.05元,而搜狐则为0.01元,因此诸如0.31的关键词竞价价格在 TOM中是不合法的。因此,有必要实现一个通用的数值验证模块,减少重复开发,提高分组开发人员的工作效率。

解决方法
用 户在Web浏览器中的输入,无论其表意是数值还是文本,均以字符串形式发送给浏览器的JavaScript引擎。所以为JavaScript的 String对象原型添加新方法,既可以满足对以字符串出现的数字的校验,又能以简易的方式完成开发任务。这个JavaScript实现的数值校验包,开 发代号是“charLes”,文件名是charles.js,下面给出它的代码以及使用示例:

/**
 * charLes 0.4.3 - Extension for JavaScript String Prototype
 * Copyright (c) 2007 Yang Yang <yangyang@myce.net.cn>
 *
 * $Date: 2007-04-11 10:53
 */
charLes = {

    //挂载库函数
    __hook: function( f ) {
        if ( f == 'all' )
            for ( var func in charLes ) {
                if ( ! /^_{1,2}/.test(func) )
                    String.prototype[func] = charLes[func];
            }
        else if ( f == 'base' ) {
            var basefunc = ['chars', 'isfixed', 'isfloat', 'isint', 'lengthin', 'notzero'];
            for ( var i in basefunc ) {
                var func = basefunc[i];
                String.prototype[func] = charLes[func];
            }
        }
        else if ( typeof f == 'object' && f.length > 0 ) {
            for ( var i in f ) {
                var func = f[i];
                if ( func in charLes ) {
                    String.prototype[func] = charLes[func];
                    for ( var i in charLes._funcDR[func] ) {
                        var dfunc = charLes._funcDR[func][i];
                        if ( dfunc != '' ) {
                            String.prototype[dfunc] = charLes[dfunc];
                        }
                    }
                }
            }
        }
    },

    //函数依赖关系
    //Function Dependency Relationship
    _funcDR: {
        'chars':    [''],
        'eq':       ['isfloat', 'isint'],
        'eqstr':    [''],
        'float':    ['isfloat', 'isint'],
        'gt':       ['isfloat', 'isint'],
        'gtstr':    [''],
        'haschar':  [''],
        'hasstr':   [''],
        'int':      ['isint'],
        'isascii':  [''],
        'isfixed':  [''],
        'isfloat':  ['notzero'],
        'isint':    [''],
        'isreal':   ['isfloat', 'isint'],
        'lengthin': ['chars'],
        'low':      [''],
        'lt':       ['isfloat', 'isint'],
        'ltstr':    [''],
        'notzero':  [''],
        'plain':    [''],
        'reverse':  [''],
        'trim':     [''],
        'up':       ['']
    },

    //返回字符个数
    chars: function() {
        //汉字算作2个字符
        return this.replace(/[^\x00-\xff]/g, 'xx').length;
    },

    //返回是否等于字符串对应的数字
    eq: function( n ) {
        if ( n == undefined || isNaN(n) )
            return false;
        else if ( this.isfloat() || this.isint() )
            return this == parseFloat(n);
        else
            return false;
    },

    //返回是否等于字符串
    eqstr: function( s ) {
        if ( s != undefined && typeof s == 'string' )
            return this == s;
        else
            return false;
    },

    //返回字符串对应的浮点数
    float: function() {
        if ( this.isint() || this.isfloat() ) {
            switch ( arguments.length ) {
                case 0:
                    return parseFloat(this);
                    break;
                case 1:
                    if ( typeof arguments[0] == 'number' ) {
                        var fix = parseInt(arguments[0]);
                        return fix > 0 ? parseFloat(this).toFixed(fix) : null;
                    } else {
                        return null;
                    }
                    break;
                default:
                    return null;
                    break;
            }
        } else return null;
    },

    //返回是否大于字符串对应的数字
    gt: function( n ) {
        if ( n == undefined || isNaN(n) )
            return false;
        else if ( this.isfloat() || this.isint() )
            return this > parseFloat(n);
        else
            return false;
    },

    //返回是否大于字符串
    gtstr: function( s ) {
        if ( s != undefined && typeof s == 'string' )
            return this > s;
        else
            return false;
    },

    //返回是否含有某字符
    //参数a可以是字符, 字符组成的字符串
    haschar: function( a ) {
        var r = true;
        //将字符串分割成字符数组, 以兼容IE
        a = typeof a == 'string' ? a.split('') : '';
        if ( a.length && a.length > 0 )
            for ( var i = 0; i < a.length; i++ )
                if ( this.indexOf(a[i]) != -1 )
                    r = r && true;
                else
                    r = false;
        else
            r = false;
        return r;
    },

    //返回是否含有某字符串
    //参数a可以是字符串, 字符串组成的数组
    hasstr: function( a ) {
        var r = true;
        //单个字符串
        if ( typeof a == 'string' )
            if ( this.indexOf(a) != -1 )
                r = r && true;
            else
                r = false;
        //字符串组成的数组
        else if ( a != undefined && a.length )
            for ( var i = 0; i < a.length; i++ )
                if ( typeof a[i] == 'string' && this.indexOf(a[i]) != -1 )
                    r = r && true;
                else
                    r = false;
        else
            r = false;
        return r;
    },

    //返回字符串对应的整数
    int: function() {
        return this.isint() ? parseInt(this) : null;
    },

    //返回是否全部为ASCII码
    isascii: function() {
        return this.replace(/[\x00-\xff]/g, '').length == 0;
    },

    //返回对应的浮点数的小数保留位是否在给定的范围
    isfixed: function () {
        switch ( arguments.length ) {
            case 1:
                if ( ! parseInt(arguments[0]) ) return false;
                if ( parseInt(arguments[0]) <= 1 ) return false;
                var p = new RegExp('^\\d+\\.\\d{1,' + parseInt(arguments[0]) + '}$');
                return p.test(this);
                break;
            case 2:
                if ( ! (parseInt(arguments[0]) && parseInt(arguments[1])) ) return false;
                if ( parseInt(arguments[0]) <= 0 ) return false;
                if ( parseInt(arguments[1]) <= 0 ) return false;
                if ( parseInt(arguments[0]) >= parseInt(arguments[1]) ) return false;
                var p = new RegExp('^\\d+\\.\\d{' + parseInt(arguments[0]) + ',' + parseInt(arguments[1]) + '}$');
                return p.test(this);
                break;
            default:
                return false;
                break;
        }
    },

    //返回是否(能转换成)浮点数
    isfloat: function() {
        switch ( arguments.length ) {
            case 0:
                var p = /^(-?0\.\d+)$|^(-?[1-9]\d*\.\d+)$/;
                return p.test(this);
                break;
            case 1:
                if ( typeof arguments[0] == 'string' ) {
                    var p = new RegExp('^('
                    + (arguments[0].indexOf('-') != -1 ? '' : '-?')
                    + '0\\.\\d+)$|^('
                    + (arguments[0].indexOf('-') != -1 ? '' : '-?')
                    + '[1-9]\\d*\\.\\d+)$');
                    if ( arguments[0].indexOf('0') != -1 )
                        return p.test(this) && this.notzero();
                    else
                        return p.test(this);
                } else {
                    return false;
                }
                break;
            default:
                return false;
                break;
        }
    },

    //返回是否(能转换成)整数
    isint: function() {
        //根据参数组合正则表达式
        switch ( arguments.length ) {
            case 0:
                var p = /^(0|-?[1-9]\d*)$/;
                return p.test(this);
                break;
            case 1:
                if ( typeof arguments[0] == 'string' ) {
                    var p = new RegExp('^('
                    + (arguments[0].indexOf('0') != -1 ? '' : '0|')
                    + (arguments[0].indexOf('-') != -1 ? '' : '-?')
                    + '[1-9]\\d*)$');
                    return p.test(this);
                } else {
                    return false;
                }
                break;
            default:
                return false;
                break;
        }
    },

    //返回是否(能转换成)实数
    isreal: function() {
        //根据参数组合正则表达式
        switch ( arguments.length ) {
            case 0:
                return this.isint() || this.isfloat();
                break;
            case 1:
                if ( typeof arguments[0] == 'string' )
                    return this.isint(arguments[0]) || this.isfloat(arguments[0]);
                else
                    return false;
                break;
            default:
                return false;
                break;
        }
    },

    //返回字符个数是否在给定范围内
    //常用于判定数据库中字符型数据(vchar, char)是否越界
    lengthin: function() {
        switch ( arguments.length ) {
            case 1:
                var n = parseInt(arguments[0]);
                if ( isNaN(n) )
                    return false;
                if ( n < 0 )
                    return false;
                return this.chars() <= n;
                break;
            case 2:
                var min = parseInt(arguments[0]);
                var max = parseInt(arguments[1]);
                if ( isNaN(min) || isNaN(max) )
                    return false;
                if ( min < 0 || max < 1 )
                    return false;
                if ( min >= max )
                    return false;
                return ( this.chars() >= min && this.chars() <= max );
                break;
            default:
                return false;
                break;
        }
    },

    //返回小写字母
    low: function() {
        return this.toLowerCase();
    },

    //返回是否小于字符串对应的数字
    lt: function( n ) {
        if ( n == undefined || isNaN(n) )
            return false;
        else if ( this.isfloat() || this.isint() )
            return this < parseFloat(n);
        else
            return false;
    },

    //返回是否小于字符串
    ltstr: function( s ) {
        if ( s != undefined && typeof s == 'string' )
            return this < s;
        else
            return false;
    },

    //返回是否非零
    notzero: function() {
        var p = /^(0+|0+\.0+)$/;
        return p.test(this) ? false : true;
    },

    //返回平整字符串
    //滤掉HTML标签, HTML注释, SQL关键字(备用), JavaScript关键字(备用)
    plain: function() {
        var html = /<\/?[a-zA-Z0-9]+[^>]*\s*\/?\s*>/g;
        var qt   = /<!--[^>]*-->/g;
        var sql  = /create|delete|drop|insert|update/ig;
        var js   = /eval|submit|var\s+/ig;
        var r = this;
        //迭代以防止字符串欺诈
        while ( html.test(r) != false || qt.test(r) != false ) {
            r = r.replace(html, '').replace(qt, '');
            html.lastIndex = qt.lastIndex = 0;
        }
        return r;
    },

    //返回反转后的字符串
    reverse: function() {
        return this.split('').reverse().join('');
    },

    //返回去除首尾空白后的字符串
    trim: function() {
        return this.replace(/^\s+|\s+$/g, '');
    },

    //返回大写字母
    up: function() {
        return this.toUpperCase();
    }
}

使用示例:

<script language="javascript" type="text/javascript" src="charles.js"></script>
<script language="javascript" type="text/javascript">
...
charLes.__hook('base'); //基础性扩展,只使用chars, isfixed, isfloat, isint, lengthin, notzero方法
var price = document.getElementById('price').value;
if (price.isfloat('-0') && price.isfixed(1,2)) alert('ok'); //如果输入值非零非负并且小数点最多保留2位,弹出'ok'
else alert('your input is invalid.'); //否则提示输入无效
...
</script>


改进方案
1. 规划名称空间,放弃直接扩展String对象原型的方式。这么做改动很小,但能避免潜在的名称空间污染。具体做法是当需要做验证时,把用户输入的数值字符 串包装成一个charLes对象,对象拥有value属性,属性对应的值即字符串本身。校验操作都围绕这个字符串展开:

function charLes(s) {
    var charLesObject = {
        value: s,
        isnumber: function() {
            if (isNaN(this.value)) {
                return false;
            } else {
                return true;
            }
        },
        ...
    }
    return charLesObject;
}
...
var price = charLes(document.getElementById('price').value);
if (price.isnumber()) alert('ok');
else alert('your input is invalid.');

2. 在1.的基础上可实现如userInput.isnumber().isfloat(’-0′).isfixed(1,2)这样的链盒式验证,提高代码可读性和开发效率。关于“链盒”可参考我翻译的窥探jQuery——面向JavaScript程序员一文。
3. 在1.的基础上,可取消挂载模式__hook(),既可以加速包装速度,又能方便地为charLes对象添加新方法。

避免此类问题的建议
1. 没有万能解药,往往业务需求决定我们需要什么样的数值校验。但作者认为本CASE在改进方案中列出的前2条建议是值得重视的。
2. 如果已形成公司自有的解决方案,应该加强技术跟进,并配有完备的文档说明。

CASE 竞价通2.0 SQL优化

类归于: SQL — admin @ 8:42 上午

主题
缩减竞价通2.0前台首页加载时间(SQL优化)。

关键词
SQL,竞价通2.0,Oracle,优化

分类
数据库

提供者
电子商务部,杨洋<yangyang@myce.net.cn>

问题描述
竞价通2.0平台,前台首页加载时间过长。主要由PHP代码执行,SQL查询,以及业务UI设计上的问题导致。我们已实施了一些缩减首页加载时间的解决方案,本CASE陈述Oracle SQL优化方面。

解决方法
先来看一下原始SQL,它向客户展现所有未查看的通知,通知分三种:1 常规系统群发通知,2 客户所属机构的机构通知,3 针对某一特定客户的通知。按时间顺序,每种通知都可能出现在通知队列中:

select notice_id,notice_title,to_char(notice_senddate,'yyyy-mm-dd') as notice_send from

(

--常规系统群发通知
select t.notice_id,t.notice_title,t.notice_content,t.notice_senddate,t.notice_effectdate
from tsm_notice t
where t.notice_userrange='1'
and t.notice_state='1'
and t.notice_issue='1'
and t.notice_effectdate<=sysdate
and (t.notice_disabled is null or t.notice_disabled>sysdate)

union

--针对某一特定客户的通知
select t.notice_id,t.notice_title,t.notice_content,t.notice_senddate,t.notice_effectdate
from tsm_notice t,tsm_u2nrelation g
where t.NOTICE_USERRANGE='0'
and t.notice_id=g.notice_id
and g.user_id=".$_SESSION['UserId']."
and t.notice_state='1'
and t.notice_issue='1'
and t.notice_effectdate<=sysdate
and (t.notice_disabled is null or t.notice_disabled>sysdate)

--客户所属机构的机构通知
union
select t.notice_id,t.notice_title,t.notice_content,t.notice_senddate,t.notice_effectdate
from tsm_notice t,tsm_u2nrelation g,user_adclient k,user_organinfo o
where t.notice_userrange='3'
and k.adclient_id=".$_SESSION['AdClientId']."
and o.organ_code=k.organ_code
and '$'||o.organ_code||'$' like '%'||'$'||g.organ_code||'$'||'%'
and t.notice_id=g.notice_id
and t.notice_state='1'
and t.notice_issue='1'
and t.notice_effectdate<=sysdate
and (t.notice_disabled is null or t.notice_disabled>sysdate)
order by notice_id desc

)

order by notice_effectdate desc,notice_send desc,notice_title asc


上述原始SQL具有一定的可读性,不难理解。但它不能查出所有符合业务逻辑的通知,并且执行效率较低。其中也有数据库结构设计方面的一些问题。下面是优化后的SQL:

select notice_id, notice_title, to_char(notice_senddate, 'yyyy-mm-dd')
as notice_send, to_char(notice_effectdate, 'yyyy-mm-dd') as
notice_effect

from tsm_notice

where notice_id in (

--在限定时间范围内,先选出所有三种通知ID
select n.notice_id
from tsm_notice n
where n.notice_effectdate<=sysdate
and (n.notice_disabled is null or (n.notice_disabled>sysdate))
and n.notice_state='1'
and n.notice_issue='1'
and n.notice_level='1'
and n.notice_userrange in ('0','1','3')

--然后剔除那些不属于针对该客户的通知ID
minus
select u.notice_id from tsm_u2nrelation u where u.user_id is not null and u.user_id!=".$_SESSION['UserId']."

--最后再剔除那些不属于针对该客户所属机构的机构通知ID,剩下的都是与该客户有关联的通知ID
minus
select u.notice_id from tsm_u2nrelation u where u.organ_code is not
null and u.organ_code!=(select organ_code from user_adclient where
adclient_id=".$_SESSION['AdClientId'].")

)

order by notice_effect desc, notice_id desc, notice_send desc


对 原始SQL做分析,发现针对每种通知的查询,都有t.notice_effectdate<=sysdate查询条件,说明对时间范围的限定是通用 的。在此基础上可以考虑竞价通2.0平台真实的数据存储量,通过在确定时间范围内查找所有符合业务逻辑的通知ID,再把通知ID作为最终父级查找的唯一条件,来得到最终想要的结果。通过分析程序逻辑和数据库数据,又发现tsm_u2nrelation表中的user_id字段与organ_code字段是 互相“排斥”的,如果user_id字段有值,则organ_code字段无值,反之亦然。如果user_id字段有值,则查到的通知ID是指向“针对某 一特定客户的通知”,如果organ_code字段有值,则查到的通知ID是指向“客户所属机构的机构通知”。
所以,区别于原始SQL的“递增方法”,优化后的SQL是从相反思路出发,做“递减查询”。这种优化可以实施是建立在两个基础上:1 与始SQL有相同的通用查询条件,即确定的时间范围;2 对真实系统中数据量的预估。

改进方案
1. 调整数据库表结构。

避免此类问题的建议
1. 对真实系统中各方面的响应速度做预估。

CASE 竞价通2.0 PHP定位时间段

类归于: PHP — admin @ 8:37 上午

主题
根据给出的某个日期,确定该日期所在周、上一周、所在月、上一月的开始日期和结束日期;并可根据中西方不同记日方法确定周的开始日期和结束日期。

关键词
PHP,竞价通2.0,时间段,日期范围,UNIX时间戳

分类
Web开发

提供者
电子商务部,杨洋<yangyang@myce.net.cn>

问题描述
在 竞价通2.0平台,客户登录后可在首页看到“门户消费状况图”,它按“本周”,“上一周”,“本月”,“上一月”四个时间段展示客户的消费状况。由于存在 中西方记日方法的差别,同一周的开始日期和结束日期会因记日方法的不同而不同。比如,西方传统记日以星期日作为一周的第一天,而通常(包括在竞价通2.0 的业务需求没有写明的情况下)我们认为星期一是一周的第一天。

解决方法
尽管竞价通2.0平台没有为客户提供设置记日习惯的功能,但用PHP实现一个可兼容不同记日方法的定位时间段函数是完全可以做到的,函数接受(假定由客户设置的)记日方法的参数,根据参数生成准确合适的时间段。实现代码如下:

/**
 * 返回本周截至当天的时间范围
 * 开始日期 'yyyy-mm-dd'
 * 结束日期 'yyyy-mm-dd'
 * 开始日期的UNIX时间戳
 * 结束日期的UNIX时间戳
 *
 * @param integer $day UNIX时间戳
 * @param string $style 默认西方日历法
 * @return array
 */
function getThisWeek($day = '', $style = 'west') {
    //当天可任意设置
    $today = is_int($day) ? $day : time();
    $today_detail = getdate($today);
    //当天所在周的第一天即周日距当天的间隔天数 按'west'风格
    $day_offset = $today_detail['wday'];
    switch ($style) {
        case 'west': //周日按第一天算起
            $day2 = $today;
            $day1 = $today - $day_offset*24*60*60;
            $day1str = date('Y-m-d', $day1);
            $day2str = date('Y-m-d', $day2);
            break;
        case 'east': //周一按第一天算起
            $day2 = $today;
            $day1 = ($day_offset == 0) ?
                    ($today - 6*24*60*60) :
                    ($today - ($day_offset-1)*24*60*60);
            $day1str = date('Y-m-d', $day1);
            $day2str = date('Y-m-d', $day2);
            break;
        default: //默认周日按第一天
            $day2 = $today;
            $day1 = $today - $day_offset*24*60*60;
            $day1str = date('Y-m-d', $day1);
            $day2str = date('Y-m-d', $day2);
            break;
    }
    return array($day1str, $day2str, $day1, $day2);
}

/**
 * 返回当天的上周时间范围
 * 开始日期 'yyyy-mm-dd'
 * 结束日期 'yyyy-mm-dd'
 * 开始日期的UNIX时间戳
 * 结束日期的UNIX时间戳
 *
 * @param integer $day UNIX时间戳
 * @param string $style 默认西方日历法
 * @return array
 */
function getLastWeek($day = '', $style = 'west') {
    //当天可任意设置
    $today = is_int($day) ? $day : time();
    $today_detail = getdate($today);
    //当天所在周的第一天即周日距当天的间隔天数 按'west'风格
    $day_offset = $today_detail['wday'];
    switch ($style) {
        case 'west': //周日按第一天算起
            $day2 = $today - ($day_offset+1)*24*60*60;
            $day1 = $day2 - 6*24*60*60;
            $day1str = date('Y-m-d', $day1);
            $day2str = date('Y-m-d', $day2);
            break;
        case 'east': //周一按第一天算起
            $day2 = ($day_offset == 0) ?
                    ($today - 7*24*60*60) :
                    ($today - $day_offset*24*60*60);
            $day1 = $day2 - 6*24*60*60;
            $day1str = date('Y-m-d', $day1);
            $day2str = date('Y-m-d', $day2);
            break;
        default: //默认周日按第一天
            $day2 = $today - ($day_offset+1)*24*60*60;
            $day1 = $day2 - 6*24*60*60;
            $day1str = date('Y-m-d', $day1);
            $day2str = date('Y-m-d', $day2);
            break;
    }
    return array($day1str, $day2str, $day1, $day2);
}


改进方案
1. 根据竞价通2.0的业务需要,getThisWeek函数只返回本周截至当天的时间范围,因为在当天之后不存在客户的消费数据。为增强通用性,可进一步修改函数,以实现返回本周完整的时间段功能。
2. 根据竞价通2.0的业务需要,getThisWeek和getLastWeek函数返回的数据组中,包含了经过格式化的日期字面值。为增强通用性,可进一步修改函数,以实现返回的数组中,只包含以UNIX时间戳为参照的秒数。而格式化的工作可交给外围程序处理。

避免此类问题的建议
1. 为客户提供可设置记日方法的功能;通常这种做法是不必要的。
2. 作为开发人员,应提供兼容不同记日方法的模块,来满足业务逻辑的需要与实现。

2009年02月24日

运用会话和对象序列化操作cookie(优化方案一)

类归于: PHP — admin @ 2:43 下午

前文给出了傻瓜型cookie管理器的实现原理和代码。现在做一下优化,可让你轻松修改、删除cookie,另外服务器端与cookie相关的会话变量可设置删除开关。废话毋讲,上代码:

<?php #Cookie.php
class Cookie {
    protected $name;
    protected $value;
    protected $expire;
    protected $path;
    protected $domain;
    protected $secure;
    protected $httponly;

    public function __construct($name, $value='', $expire=0, $path='/', $domain='', $secure=false, $httponly=false) {
        $this->name = $name;
        $this->value = $value;
        $this->expire = $expire;
        $this->path = $path;
        $this->domain = $domain;
        $this->secure = $secure;
        $this->httponly = $httponly;
        $this->setcookie(); // client side
        $this->store(); // server side
    }

    function __sleep() {
        return array_keys(get_object_vars($this));
    }

    function __wakeup() {
        $this->value = ''; // automate clean work
    }

    public function store() {
        $_SESSION['cookie'][$this->name] = serialize($this);
    }

    protected function setcookie() { // setting and deleting
        if (phpversion() >= '5.2.0') {
            setcookie($this->name, $this->value, $this->expire, $this->path, $this->domain, $this->secure, $this->httponly);
        } else {
            setcookie($this->name, $this->value, $this->expire, $this->path, $this->domain, $this->secure);
        }
    }

    public function destroy($completely = false) {
        $this->setcookie(); // client side
        if ($completely) { // server side
            unset($_SESSION['cookie'][$this->name]);
        }
    }
}
?>
<?php #start.php
require_once 'Cookie.php';
session_start();
$blogger = new Cookie('blogger', 'Yang Yang');
$muscian = new Cookie('musician', 'Ecalo, Island Cycle');
?>
<?php #display.php
session_start();
var_dump($_SESSION);
var_dump($_COOKIE);
?>
<script type="text/javascript">
document.write(document.cookie);
</script>
<?php #destroy.php
require_once 'Cookie.php';
session_start();
foreach ($_SESSION['cookie'] as $cookie) {
    $cookie = unserialize($cookie);
    $cookie->destroy(true);
}
?>

2009年02月19日

PHP套接字编程:请求部分资源

类归于: PHP — admin @ 3:30 下午

在HTTP 1.1报文规范中,请求首部(Request Header)定义了Range字段,它允许请求获取资源中的部分内容。语法如下:

       ranges-specifier = byte-ranges-specifier
       byte-ranges-specifier = bytes-unit "=" byte-range-set
       byte-range-set  = 1#( byte-range-spec | suffix-byte-range-spec )
       byte-range-spec = first-byte-pos "-" [last-byte-pos]
       first-byte-pos  = 1*DIGIT
       last-byte-pos   = 1*DIGIT

HTTP 1.1明确定义了bytes-unit是‘bytes’。响应信息以字节为单位,first-byte-pos为0,表示响应信息的第一个字节。若想获取前100个字节,那么Range字段值应为‘bytes=0-99’。

以本站为例,使用PHP套接字函数建立网络客户端与服务器之间的连接,发送请求报文并接收响应报文:

<?php
error_reporting(E_ALL);
ini_set('display_errors', 'On');
set_time_limit(0);

#准备所需参数
$domain = 'www.swallowtable.com';
$ip_address = gethostbyname($domain);
$port = getservbyname('www', 'tcp');

#创建套接字并连接
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket === FALSE) {
    echo 'socket_create() failed: ' . socket_strerror(socket_last_error()) . '<br>';
}
$result = socket_connect($socket, $ip_address, $port);
if ($result === FALSE) {
    echo 'socket_connect() failed: ' . socket_strerror(socket_last_error()) . '<br>';
}

#创建请求并发送
$header = "GET / HTTP/1.1\r\n";                //请求行
$header .= "Connection: close\r\n";            //通用首部
$header .= "Host: $domain\r\n";                //请求首部
$header .= "Range: bytes=0-9\r\n\r\n";         //请求首部,请求获取前10个字节的内容
socket_write($socket, $header, strlen($header));

#接收响应并输出
$output = '';
while ($output = socket_read($socket, 2048)) {
    echo $output;
}
socket_close($socket);
?>

来看看响应输出的信息:

HTTP/1.1 206 Partial Content
Date: Thu, 19 Feb 2009 05:28:16 GMT
Server: Apache/2.2.9 (Debian) PHP/5.2.6-2ubuntu3 with Suhosin-Patch mod_perl/2.0.4 Perl/v5.10.0
Last-Modified: Mon, 25 Aug 2008 05:11:56 GMT
ETag: "164c0e-58-45541d378ff00"
Accept-Ranges: bytes
Content-Length: 10
Vary: Accept-Encoding
Content-Range: bytes 0-9/88
Connection: close
Content-Type: text/html

<html>

响应代码206表示服务器已满足对部分资源的GET请求,并包括了相应的信息。我们还可以从响应头部看到,资源(响应实体内容)总共88个字节。我们可以修改请求报文来获取全部内容:

$header = "GET / HTTP/1.1\r\n";                //请求行
$header .= "Connection: close\r\n";            //通用首部
$header .= "Host: $domain\r\n";                //请求首部
$header .= "Range: bytes=0-87\r\n\r\n";        //请求首部,实际上获取了全部内容

再来看看响应输出的信息:

HTTP/1.1 206 Partial Content
Date: Thu, 19 Feb 2009 06:24:46 GMT
Server: Apache/2.2.9 (Debian) PHP/5.2.6-2ubuntu3 with Suhosin-Patch mod_perl/2.0.4 Perl/v5.10.0
Last-Modified: Mon, 25 Aug 2008 05:11:56 GMT
ETag: "164c0e-58-45541d378ff00"
Accept-Ranges: bytes
Content-Length: 88
Vary: Accept-Encoding
Content-Range: bytes 0-87/88
Connection: close
Content-Type: text/html

<html>
<head>
<title>youbushufule</title>
</head>
<body>
youbushufule
</body>
</html>

嘎~嘎~

有些服务器或客户端可能会忽略Range请求,这就要看你的运气了。博主我试了新浪、豆瓣和国家地理英文网站,都不支持,郁闷啊。

请求部分资源对于编写确定目标的爬虫程序是有帮助的。假如国家地理的Photo Of The Day所在的服务器支持Range请求,那么我们就可以预先估算照片url所在的字节范围,最大限度地缩小传输数据的尺寸。So far so good!

2009年02月18日

生成随机验证码图片(PHP实现)

类归于: PHP — admin @ 2:32 下午

前不久博主在开发自己的一个Web应用,实现了随机验证码功能。这次就来跟大家分享一下设计思路和实现代码,首先忽略在服务器端设置会话变量的过程,单就验证码及验证码图片的生成跟大家做个交代,ah-oh-ah-oh。

众所周知,验证码常见于带有身份认证机制的Web应用程序。利用在服务器端随机生成的明码来促成客户端使用者与服务器端应用程序的握手和身份识别(这里是指人机间的交互),从基于LAMP架构的系统来讲,它的实现有赖于会话管理和GD库的支持,以及开发者的个人审美情趣。关于GD库的支持请大家参考PHP官方发行包中的安装指南(install.txt)。接下来,请大家跟我一道,参观验证码展厅展出的展品。

首先大家看到的可能是一些无序的阿拉伯数字以及英文字母,它们随机排列组合在一起,形成一种可能给观赏者带来不适反应的暗语。作为这些已经登堂入室的艺术作品的创作者,你应该避免走抽象派路线。通俗地说,就是应该尽量摒弃那些容易造成视觉混乱的元素。你可以异想天开地把不同字体样式应用在诸如0,O,o,1,l上,但这已经偏离了我们为人民服务的宗旨,那就是全心全意。废话毋讲,先来看看我们实现的代码:

/**
 * 获取随机字符串(由英文字母和数字组成)
 *
 * @param int $len 字符串长度
 * @return string
 */
function get_random_string($len = 5) {
    $list = array_merge(range('A', 'N'), /* skip letter O */ range('P', 'Z'), /* skip digit 0,1 */ range(2, 9));
    $str = '';
    while ($len) {
        $str .= $list[rand(0, 32)];
        --$len;
    }
    return $str;
}

嗯,很明显我们严格地把了关,剔除了两对双胞胎中的三个,并命令剩下的一个在步入成年后才能去验证码大军服役。这样做的好处显而易见,人民在视觉上会更有安全感!那么接下来需要考虑的就是如何为人民子弟兵们布阵、添加迷彩。为此我们开发了一个功能高度集成的函数generate_valicode:

/**
 * 生成验证码图像
 *
 * @param string $str 随机字符串
 * @param int $width 图像宽度
 * @param int $height 图像高度
 */
function generate_valicode($str, $width = 120, $height = 120) {
    $im = imagecreatetruecolor($width, $height);
    $degree_font = range(-10, 10);
    $degree_im = array_merge(range(-30, -10), range(10, 30));
    $font = 5;
    $font_width = imagefontwidth($font);
    $font_height = imagefontheight($font);
    $color_bg = imagecolorallocate($im, 0xff, 0xb8, 0x91);
    $color_bgd = imagecolorallocate($im, 0xef, 0xac, 0x88);
    $color_white = imagecolorallocate($im, 0xff, 0xff, 0xff);
    imagefill($im, 0, 0, $color_bg);
    $codes = str_split($str);
    $y = floor(($height-$font_height)/2);
    $x = floor(($width-count($codes)*$font_width)/2);
    imageline($im, 0, 0, $width, 0, $color_white);
    imageline($im, 0, 0, 0, $height, $color_white);
    imageline($im, 0, 0, $width, $height, $color_white);
    imageline($im, 0, 0, floor($width/2), $height, $color_white);
    imageline($im, 0, 0, $width, floor($height/2), $color_white);
    do {
        $code = current($codes);
        list($angle) = get_random_element($degree_font);
        imagettftext($im, 15, $angle, $x+1, $y+1, $color_bgd, FONT_COURIER, $code);
        imagettftext($im, 15, $angle, $x, $y, $color_white, FONT_COURIER, $code);
        imageline($im, 0, ord($code), ord($code), 0, $color_white);
        $x += 12;
    } while (next($codes) !== FALSE);
    list($rotate) = get_random_element($degree_im);
    $imr = imagerotate($im, $rotate, $color_bg);
    $thumb = imagecreatetruecolor($width, $height);
    imagecopyresized($thumb, $imr, 0, 0, 0, 0, $width, $height, $width, $height);
    header('Content-type: image/png');
    imagepng($thumb);
    imagedestroy($im);
    imagedestroy($imr);
    imagedestroy($thumb);
}

具体来说,我们让每个字符都有随机的倾斜角度,保证它们在合理的可识别的范围内“顾秋”。然后再根据每个字符对应的十进制ASCII码表编号,绘制金字塔上的水平切割线,造成轻微的识别干扰(说来话长并令人惊奇,数学的极至之美在这里得到充分体现,博主尚未花心思做研究,只是凭着感觉做出这种看起来像是经过精确计算的干扰效果)。最后再把整幅图像做随机的倾斜处理,并输出为PNG格式。

自定义常量FONT_COURIER是指向服务器系统中ttf字体的绝对路径,配合imagettftext函数,我们可以绘制出平滑的字体。

另外,我们在generate_valicode函数里用到了一个自定义函数get_random_element,它会以数组形式返回给定集合的一个子集,子集中的元素是随机取得且彼此相异的,默认集合元素个数是1。get_random_element函数如下所示,博主给出了两种实现方式,第一种足够用,第二种在父集和子集元素个数均较大的情况下会有更好的执行效率(大家可自行研究,这里就不做话题扩展了):

/**
 * 从数组中随机取出元素
 *
 * @param array $set 数组
 * @param array $num 元素数量
 * @return array
 */
function get_random_element($set, $num = 1) {
    shuffle($set);
    return array_slice($set, 0, $num);
}

/**
 * 从数组中随机取出元素
 *
 * @param array $set 数组
 * @param array $num 元素数量
 * @return array
 */
function get_random_element_fast($set, $num = 1) {
    $sub_set = array();
    foreach ((array)array_rand($set, $num) as $i) {
        $sub_set[] = $set[$i];
    }
    return $sub_set;
}

验证码图像的显示效果,大家可自行在本地环境中运行代码查看。

2009年02月17日

运用会话和对象序列化操作cookie

类归于: PHP — admin @ 5:42 下午

在PHP中,设置cookie非常方便,运用setcookie函数可以向浏览器发送所需的持久化数据,同时它也可以删除保存在客户端的cookie。如果稍加分析,你会发现,cookie首要考虑的是它的保存位置。为便于记忆,我们在这里使用自顶向下的方法来弄清楚setcookie函数所需的参数及它们的意义。请看:

http(s)://www.sample.com/sub/setup.php

如你所见,上面的url很具有代表性,我们可以把它分解为几个部分:协议(http或https)、域名(sample.com)、子路径(/sub/)、以及当前执行脚本(setup.php)。仔细观察PHP手册中setcookie函数的参数,你会发现把它们倒序组合起来,几乎就是一个url地址。

bool setcookie  ( string $name  [, string $value  [, int $expire=0  [, string $path  [, string $domain  [, bool $secure=false [, bool $httponly=false  ]]]]]] )

由此,我们可以在此基础上加深记忆setcookie函数的用法:一个近乎完美的cookie=名值对+生命周期+保存位置。

如果你仍觉得setcookie函数过于复杂,呵呵,也许你是对的!废话毋多讲,下面进入高阶话题,博主教你如何运用OOP、Session和Serialization操作cookie,从而摆脱繁复惨淡的“糕点生意”。

在我们智力劳动之前,先来阐述如此大刀阔斧的原因。众所周知,cookie有自己的生命周期,在PHP中,销毁cookie的操作要参照创建cookie时的细节,这几乎算不上“窍门”,只能看作是PHP语言的“圣旨”,而且会让人觉得笨拙。先看下示例代码:

//创建cookie
setcookie('blogger', 'Yang Yang(aka davidkoree)', time()+3600, '/blog/', '.swallowtable.com', false, false);
//删除cookie
setcookie('blogger', '', time()+3600, '/blog/', '.swallowtable.com', false, false);

如果把cookie的值设置为空字符串或FALSE,并且保证其他参数与创建cookie时的参数相匹配,那么就可以显式地删除cookie。如果你在开发大型Web应用程序,需要进行大量的cookie操作,那么无疑这种方法会消耗你许多精力,因为你需要知晓每个cookie的参数个数以及具体数值。如前所述,博主要教你相对好一些的方法来操作cookie(只要大家细心钻研,一定可以在理解此篇博文的基础上找到更好的解决方案)。

下面阐述优雅操作cookie的具体思路。首先,我们需要一种方式来结构cookie,引入OOP可构建出cookie的映射对象,同时,对象内置的__sleep()和__wakeup()方法专门用来在序列化和反序列化的过程中改变cookie对象的行为或属性。其次,为了简化删除cookie操作的复杂性,我们需要将序列化的cookie对象保存在会话中,方便提取。为了测试,我们在本地开发环境中建立四个文件,CookieManager.php是cookie的映射对象,setup.php负责创建cookie,display.php用来查看本地cookie,destroy.php负责销毁cookie。代码如下:

<?php #CookieManager.php

class Cookie {

    var $path;
    var $name;
    var $value;
    var $expire;

    function __construct($name, $value, $expire = 0, $path = '/') {
        $this->name   = $name;
        $this->value  = $value;
        $this->expire = $expire;
        $this->path   = $path;
    }

    function destroy() {
        $this->value = '';
    }

    function __wakeup() {
        $this->destroy();
    }

    function __sleep() {
        return array('name', 'value', 'expire', 'path');
    }
}
?>
<?php #setup.php
require_once 'CookieManager.php';

$name = 'blogger';
$value = 'Yang Yang(aka davidkoree)';
$expire = time()+3600;
$path = '/';

setcookie($name, $value, $expire, $path);
$blogger = new Cookie($name, $value, $expire, $path);

session_start();
$_SESSION['blogger'] = serialize($blogger);
?>
<?php #display.php
session_start();
?>
<script type="text/javascript">
document.write(document.cookie);
</script>
<?php #destroy.php
require_once 'CookieManager.php';
session_start();

$blogger = unserialize($_SESSION['blogger']);
setcookie($blogger->name, $blogger->value, $blogger->expire, $blogger->path);
?>

__sleep()方法在cookie对象$blogger被序列化之前执行,这里我们仅告诉$blogger把需要用到的成员写入字节流。在稍后的反序列化过程里,这些成员会从字节流中提取出来,用于构造setcookie函数的参数。

一旦我们需要删除某个cookie,我们就要把与它相应的cookie对象反序列化。cookie对象以字节流的形式保存在会话中。__wakeup方法在cookie对象被反序列化之前执行,这里我们让$blogger的value成员变成空字符串。然后便可以使用setcookie函数删除$blogger对应的cookie了!

在调用了setup.php之后,我们可以看看display.php显示的本地cookie信息:

PHPSESSID=r5v40cqtsthhgsr709lsp5qal3; blogger=Yang+Yang%28aka+davidkoree%29

然后我们使用destroy.php删除‘blogger’这个cookie,再看看display.php显示的本地cookie信息:

PHPSESSID=r5v40cqtsthhgsr709lsp5qal3

啊哈,操作成功~

博主这厮只是抛砖引玉,希望不要挨大家的砖头就好!相信还有更绝妙的管理cookie的方法,欢迎多多交流!

早前文章 »

WordPress 所驱动