文章列表
 
您正在查看 "Php" 分类下的文章

2010年07月31日 星期六 下午 6:38
欢迎访问我的新主页:http://huoding.com/

作者:老王

PHP代码审计方面的软件越来越多了,PHPCheckstyle算是最近比较活跃的一个。通过SVN钩子脚本的方式来调用PHPCheckstyle,可以强制代码必须符合预先设定的编码标准(比如PEAR编码标准),有助于在多人合作项目中提高代码整体质量。

PHPCheckstyle的设置:

安装真的没什么可说的,属于接插即用型的,唯一需要设置的就是config目录下的配置文件:缺省使用的是default.cfg.xml,你可以编辑它,按照官方文档适当的增减规则。不过PHPCheckstyle项目诞生时间短,不够稳定,截至0.8版本为止还有不少问题,使用前最好逐条规则进行测试。

最简单的运行方法如下:

php run.php --src /path/to/file

这样的话会生成相关的html文档,如果你想直接输出的话,请使用:

php run.php --format console --src /path/to/file

更多选项可以自己看帮助(php run.php就可以查看相关帮助)

Subversion钩子脚本:

下面设置钩子脚本,具体点说是前置钩子,也就是:pre-commit,通过钩子检查后才被允许提交到版本库。只有添加或更新的文件是需要检查的,如果是要删除的文件,则没有必要检查;还有一个问题,PHPCheckstyle只能检查具体文件的内容,而在提交之前,我们想要检查的文件还不存在,所以我们得生成一个临时文件,检查完再删除,另外,在生成文件时要注意其唯一性,免得多用户一起提交时发生冲突,注意事项了解的差不多了,可以写钩子脚本了:

代码(at pastebin.com):

01 #!/bin/bash
02
03 REPOS="$1"
04 TXN="$2"
05
06 PHP="/usr/local/php/bin/php"
07 SVNLOOK="/usr/bin/svnlook"
08
09 RUNSCRIPT="/path/to/run/php/script"
10
11 CHANGED=`$SVNLOOK changed -t "$TXN" "$REPOS" | grep '^[U|A]' | awk '{print $2}'`
12
13 for FILE in $CHANGED; do
14     if [[ "$FILE" =~ \.php$ ]]; then
15         TEMPFILE=`mktemp`
16         $SVNLOOK cat -t "$TXN" "$REPOS" "$FILE" > $TEMPFILE
17         MESSAGE=`$PHP $RUNSCRIPT --format console --src $TEMPFILE | head -n -2`
18         if [ ! -z "$MESSAGE" ]; then
19             rm -rf $TEMPFILE
20             echo "$MESSAGE" | sed -e "s|$TEMPFILE|$FILE|" 1>&2
21             exit 1
22         fi
23         rm -rf $TEMPFILE
24     fi
25 done

关于Shell,如果有不清楚的可以自己搜索一下,网上有很多类似的文章

钩子脚本还可以做很多事情,比如核对PHP脚本语法(php -l),而且通过管道符不用生成临时文件:

MESSAGE=`$SVNLOOK cat -t "$TXN" "$REPOS" "$FILE" | $PHP -l`

运行后,不用判断MESSAGE是否为空,而是根据退出状态来判断脚本是否有语法问题:

if [ $? -ne 0 ]

PHPCheckstyle配置和使用多少还是有点别扭,有机会试试PHP_CodeSniffer配置钩子脚本更简单

BTW:发现一个PHP Commit Hooks项目,有点意思,可以看看。
 
2010年07月14日 星期三 下午 9:21
欢迎访问我的新主页:http://huoding.com/

作者:老王

今天翻了翻《领域驱动设计与模式实战》,里面详细讲解了“状态模式”,说来我对它并不陌生,几年前,我在看《Java与模式》的时候就仔细研究过,不过这么多年来却从没在实战中应用过,并不是没有遇到合适的场景,即便在前几天结束的一个项目中,还涉及订单状态来着,可我却是用最俗的if/else编码的,书算是白看了,为了不让悲剧重演,我决定重新温习一下状态模式,加深一下印象。

无图无真相,下面看看订单在生命周期里的状态迁移:



如图所示:订单状态有New Order,Registered,Granted,Shipped,Invoiced,Cancelled,相当复杂,在不同的状态执行操作时会产生不同的影响,比如说我们要执行AddOrderLine的话,要判断订单状态,如果是Registered或Granted状态的话,订单状态会变成New Order,如果是New Order状态的话,则状态保持不变,如果是其它状态的话,则不允许AddOrderLine操作。

如果我们不使用状态模式的话,那么代码里免不了充斥着if/else,所有涉及状态的操作都会被拖累,这还不算,一旦要是加入新的状态(比如说加入一个退货状态),你不得不修改原有的代码,挨个加上elseif,方法代码会变得越来越冗长,开闭原则算是没戏了,维护这样的代码,早晚有一天会崩溃的。

下面看看状态模式是如何解决此类问题的,见UML图:




引入OrderState对象,原有的SalesOrder对象中的方法,只要涉及状态的,如:AddOrderLine等,都委派给具体的OrderState对象处理,从而避免了if/else的坏味道,这也是多态的威力所在。说到这里,有些读者可能会把状态模式和策略模式搞混了,确实,它们很相像,策略模式同样可以替换掉代码里if/else的坏味道,至于二者的区别,主要在于状态模式中,对象有明显的状态迁移,比如说用户有登录状态,在登陆前是未登录状态,在登陆后是登录状态,这里有明显的状态迁移;至于策略模式,则主要是算法的分离,而不存在状态的迁移,比如说下馆子结账时,满100元九折,满200元八折,就是策略模式,这里两个打折策略只能选一个,非此即彼,不存在迁移的可能性,有时候饭馆会进行累计消费的优惠,可能这次打九折,下次打八折,看似状态迁移了,但顾客的多次就餐行为通常是独立的,所以这是两个对象生命周期中的状态,而非一个对象生命周期中的两次状态迁移。

回到前面的订单例子,看看当执行AddOrderLine时,订单如何从Registered状态迁移到NewOrder状态:

代码(at pastebin.com):

01 class SalesOrder
02 {
03     protected $state;
04
05     public function __construct()
06     {
07         $this->state = new Registered($this);
08     }
09
10     public function setState($state)
11     {
12         $this->state = $state;
13     }
14
15     public function AddOrderLine()
16     {
17         $this->state->AddOrderLine();
18     }
19
20     // ...
21 }
22
23 abstract class OrderState
24 {
25     protected $order;
26
27     public function __construct($order)
28     {
29         $this->order = $order;
30     }
31
32     public function AddOrderLine()
33     {
34         // ...
35     }
36
37     // ...
38 }
39
40 class NewOrder extends OrderState
41 {
42     public function AddOrderLine()
43     {
44         // ...
45     }
46 }
47
48 class Registered extends OrderState
49 {
50     public function AddOrderLine()
51     {
52         $this->order->setState(new NewOrder($this->order));
53     }
54
55     // ...
56 }
57
58 $salesOrder = new SalesOrder();
59
60 var_dump($salesOrder);
61
62 $salesOrder->AddOrderLine();
63
64 var_dump($salesOrder);


说明:订单状态对象的实例化必须在订单对象中完成,这是因为订单状态对象如果单独存在的话是没有意义的,它的生命周期依赖于订单对象。

如果有兴趣的可以继续参考”A Head Start on Domain-Driven Design Patterns“。
 
2010年07月05日 星期一 下午 9:30
欢迎访问我的新主页:http://huoding.com/

作者:老王

自动加载的陷阱

class_exists缺省情况下会触发autoload,如果你没注意到这点的话很可能会吃亏,演示代码:

spl_autoload_register(function($name) { echo $name; }); class_exists('foo');

你可以通过in_array($name, get_declared_classes())函数来判断是否存在相关的class,这样不会触发autoload,不过稍显笨重,其实class_exists()函数本身可以不触发autoload,方法是第二个参数:class_exists('foo', false);,不过老实说,当初设计的时候缺省值是true实在是个错误的决定。

BTW:method_exists也要注意,不过它没有类似class_exists那样能关闭autoload的参数控制,这一点在手册里已经明确写出来了,需要注意:

Note: Using this function will use any registered autoloaders if the class is not already known.

所以如果你不想触发autoload,那么在使用method_exists之前,必须确保对应的类已经加载,否则就没戏了。

缓存代码的重复味道

缓存在Web程序里必不可少,最常见的形式如下:

01 class Foo extends DAO
02 {
03     public function find_by_a()
04     {
05         $result = $this->cache->get('cache_a');
06
07         if (!$result) {
08             $result = $this->db->getAll('select ... from ... where a ...');
09
10             $this->cache->set('cache_a', $result);
11         }
12
13         return $result;
14     }
15
16     public function find_by_b()
17     {
18         $result = $this->cache->get('cache_b');
19
20         if (!$result) {
21             $result = $this->db->getAll('select ... from ... where b ...');
22
23             $this->cache->set('cache_b', $result);
24         }
25
26         return $result;
27     }
28 }


这个代码很平常,实际情况中,多数人差不多都是这么写代码,先用某个键在缓存里取一下,如果没有就从数据库里实际查询一次,并且把结果缓存起来,这样的代码虽然不够健壮(没有捕捉可能存在的异常),不过本身并没有太大问题,但是若干个方法叠加起来,我们就能明显的感受到坏味道:重复!不说废话了哦,直接给出解决方案:

01 abstract class DAO
02 {
03     public function getCache($key, $closure)
04     {
05         $result = $this->cache->get($key);
06
07         if (!$result) {
08             $result = $closure();
09
10             $this->cache->set($key, $result);
11         }
12
13         return $result;
14     }
15 }
16
17 class Foo extends DAO
18 {
19     public function find_by_a()
20     {
21         return $this->getCache('cache_a', function() {
22             return $this->db->getAll('select ... from ... where a ...');
23         });
24     }
25
26     public function find_by_b()
27     {
28         return $this->getCache('cache_b', function() {
29             return $this->db->getAll('select ... from ... where b ...');
30         });
31     }
32 }

代码有点简陋,通过把非公共代码提取成一个closure,传递给getCache方法,从而消除了重复的坏味道。
 
2010年07月02日 星期五 下午 7:21
欢迎访问我的新主页:http://huoding.com/

作者:老王

动态语言的必杀技之一就是能动态修改对象。不过PHP在这方面有缺陷,不能随心所欲,比如说不能很方便的给对象动态添加一个新方法。不过通过魔术方法可以在一定程度上改善这个问题,这也不算什么新鲜事儿,很多PHP项目都做过有益的尝试,比如CakePHP在Model的Behavior中的尝试,不过今天要说的方式在魔术方法的基础上引入了PHP5.3的新功能:closure,从而让实现效果更像Javascript这种prototype风格。

代码(at pastebin.com):

01 abstract class ClassAbstract
02 {
03     protected $closures = array();
04
05     public function __get($name)
06     {
07         if (isset($this->closures[$name])) {
08             return $this->closures[$name];
09         }
10
11         throw new Exception('Undefined property: ' . $name);
12     }
13
14     public function __set($name, $value)
15     {
16         if (get_class($value) == 'Closure') {
17             $this->closures[$name] = $value;
18         } else {
19             $this->$name = $value;
20         }
21     }
22
23     public function __call($name, $arguments)
24     {
25         if (isset($this->closures[$name])) {
26             array_unshift($arguments, $this);
27             return call_user_func_array($this->closures[$name], $arguments);
28         }
29
30         throw new Exception('Undefined method: ' . $name);
31     }
32 }
33
34 class Foo extends ClassAbstract
35 {
36     private $attribute = 'hello, world.';
37
38     public function attribute()
39     {
40         return $this->attribute;
41     }
42 }
43
44 // hello, world. I'm LaoWang.
45
46 $foo = new Foo();
47
48 $foo->test = function($self, $name) {
49     return $self->attribute() . " I'm {$name}.";
50 };
51
52 echo $foo->test('LaoWang');


在动态添加方法的时候,我们使用这样的语法:

$foo->new_function = function($self, ...) { ... };

具体点说,对象本身($this)要作为第一个参数($self)传递进去,不过在调用方法的时候可以无视这个参数,程序会自动传入$this,这虽然多少有点不透明,但也是无奈之举,况且,如果你喜欢Python的实现方式,说不定还会觉得这个方式很亲切。

当然,如果你想替换旧方法,对PHP而言,没戏!能添加新方法就感激不尽了,大家就知足吧。

补充:这里有个问题,就是新加的方法是针对当前对象而言的,如果重新实例化一个对象,是没有新加的方法的,可以稍加修改来实现这一点,具体点来说,就是在父类中方式一个静态闭包容器,添加新方法是使用静态调用的方式,剩下的魔术方法部分雷同,如此即可。
 
2010年06月25日 星期五 下午 9:44
欢迎访问我的新主页:http://huoding.com/

作者:老王

Page Flushing不是啥新概念,几年前雅虎就在Best Practices for Speeding Up Your Web Site里提过Flush the Buffer Early,可惜我一直没太在意,不过今天在翻看Velocity 2010 Speaker Slides & Video中的资料时,发现了Building Performance Into the New Yahoo! Homepage,里面再次强调了这个问题。

Page Flushing的目的在于让让浏览器尽可能快的得到数据,以便渲染,在PHP里有一个flush函数很有用:

<!-- css -->
</head>
<?php flush(); ?>
<body>
<!-- content -->


通过在head后面放置flush,即便后面的内容还没有生成,也可以先让客户端得到头部的内容,从而尽快开始下载css数据(这里我没说js,因为从完美角度看,js应该尽可能放在页面尾部,而不是放在head里),如此一来速度会快一些,从而改善前端的用户体验。

在head后放置flush是最常见的方法,但不便于演示,下面看看在body中使用flush的例子:

<html>
<head>
<title>DEMO</title>
</head>
<body>
<div>hello</div>
<?php ob_flush(); flush(); sleep(1); ?>
<div>world</div>
<?php ob_flush(); flush(); sleep(1); ?>
</body>
</html>


注意:因为有的PHP环境会打开输出缓冲,所以上面使用了ob_flush方法排除这种情况。

使用Firefox浏览上面代码,就能看到效果了,但是如果你使用IE浏览的话,会发现无效,原因在PHP手册中对flush函数的描述里已经说得很清楚了:

Some versions of Microsoft Internet Explorer will only start to display the page after they have received 256 bytes of output, so you may need to send extra whitespace before flushing to get those browsers to display the page.

也就是说 如果内容太短的话,IE会先缓冲内容,到了一定的长度再输出,如果你遇到了这个问题,可以发送空白字符来凑数。

最后再说一个问题:

<html>
<head>
<title>DEMO</title>
</head>
<body>
<table>
<tr><td>

long string...
<?php ob_flush(); flush(); sleep(1); ?>
</td></tr>
<tr><td>
long string...
<?php ob_flush(); flush(); sleep(1); ?>
</td></tr>
</table>
</body>
</html>


这段代码和上面的代码相比,多了一个页面级的table包裹,如果这样设计话的,即便使用了flush,但由于table标签标签未闭合,所以浏览器不会立即开始渲染,必须等到table标签闭合才可以,如此一来,flush就无意义了,不过经过测试,我发现虽然IE有这个问题,但Firefox似乎不受影响,可不管怎么说,在使用Page Flushing技术的时候,还是应该尽可能避免页面级别的元素包裹,把内容打散,多多使用flush。

补充:flush仅在php作为apache模块安装时才有效,如在ngxin下以fastcgi方式安装php,那么当运行flush的时候,缺省无效果,nginx通过fastcgi_buffers自己控制输出,按照官方文档描述,可以通过设置fastcgi_max_temp_file_size为0来关闭fastcgi_buffers,但经过测试发现还是不能达到预期效果,如果想达到类似的效果,必须设置一个较小的fastcgi_buffer_size,并且关闭gzip。

补充:很多模板引擎有block功能,可以在block开始和结束的时候加上钩子,整合这个功能。

链接:


深入理解 ob_flush和flush的区别
PHP实时输出报文到浏览器
 
   
 
 
文章存档
 
     
 
最新文章评论
  

[表情]
 

不错!
 

linux大师之路,www.linuxmr.com
 

引导一直没有整明白说。
 

[表情]
   
帮助中心 | 空间客服 | 投诉中心 | 空间协议
©2012 Baidu