Jump to navigation

You are currently browsing all posts tagged with 'php'

PHP垃圾回收机制 之 如何进行垃圾回收

  • Posted on September 8, 2010 at 1:31 pm

原文地址: http://derickrethans.nl/collecting-garbage-cleaning-up.html
前面讲过,关于PHP垃圾回收机制,原作者写了三篇文章,
第一篇讲的是 php垃圾回收机制之变量的处理
这是第二篇,主要讲PHP5.3如何进行垃圾回收。

在上一篇里面,我们遇到了 circular reference的问题,导致了内存泄露。
本文主要来解决这个问题。
来回忆一下吧:

$a =  array("one");
$a[] = &$a;
unset($a);

PHP垃圾回收机制 之 如何进行垃圾回收

一直以来,reference counting memory mechanisms 引用计数机制都存在这个问题,
直到2007年,David F. Bacon和V.T. Rajan 写了一篇论文”Concurrent Cycle Collection in Reference Counted Systems“,解决了这个问题。

于是Derick Rethans以及某些人一起,按照这篇论文,在PHP 5.3版本源代码里面解决了circular reference导致内存泄露的问题。

下面简单讲解一下这套算法:
首先,介绍一下背景知识:
如果zval容器的refcount增加了,说明有变量(符号)正在使用它,所以它肯定不是垃圾。
如果zval容器的refcount减少到0了,这个容器会被删除掉,空间被释放出来。
综上所述,只有在refcount减少到一个非零值的时候,才有可能存在垃圾回收的问题。

如果上面讲的知识您不太明白,那么请去复习一下关于垃圾回收的第一篇文章:php垃圾回收机制之变量的处理

其次,在垃圾回收过程中,怎样来分辨哪些zval容器是需要回收的垃圾呢?
办法就是:让数组所有元素的zval容器的refcount减一。如果最终数组指向的zval的refcount值为0,那么此数组就是垃圾。正常情况下,最终的refcount应该为1。
挺难理解的吧,看图:

PHP垃圾回收机制 之 如何进行垃圾回收

花荣的说明:图中的a符号,我也不知道是怎样来的。。。。。。反正它不是原来的数组a了。
就假设它是另外一个新的变量好了,它指向array(0),因此array(0)的zval的refcount从1变成了2。
另外,所有zval的默认颜色都是黑色。

如果每当refcount减少的时候,都去完成一次垃圾回收过程,未免效率不高。
此算法准备了一个root buffer(也就是一个缓冲区),当一个zval容器的refcount减少到一个非零值的时候,
就把这个zval扔到root buffer里面,然后标记此zval为粉色。
同时还要保证,每个zval只会被扔进来一次~~。
使用了颜色之后,确实方便,只扔黑色的zval进来就可以了。
只有当root buffer被充满的时候(大概需要一万个zval容器才能充满它),
垃圾回收才开始启动。如上图中的A步骤所示。

在上图中的B步骤里,垃圾回收开始启动,
对于root buffer中的每一个zval,
都要使用深度优先的搜索算法(depth-first search),寻找它的所有元素的zval(在这个例子里面,就是数组的所有元素),
并且把元素的zval的refcount减一,然后标记被减一的zval为灰色。

在C步骤里面,依然是对于root buffer中的每一个zval,
都使用深度优先的搜索算法,寻找它的所有元素的zval。
如果元素的zval的refcount是0,就把这个zval标记为白色(在上图中以蓝色表示)。
如果元素的zval的refcount大于0,就让它以及它的所有子元素的zval的refcount加一,恢复原值,标记为黑色。这一步使用的仍然是深度优先算法。

步骤D,清空root buffer。同时把所有标记为白色的zval删除掉,释放空间。

PS:不知道我讲明白没有,反正当时我是看了好久才明白过来。
在此感谢 http://blog.csdn.net/phpkernel/archive/2010/07/14/5734743.aspx 此文作者。他从源代码的角度对GC进行了分析。有一定的可取之处。

结合原来的数组a,我们再来分析一下这个过程。
为了方便,我们把数组的zval命名为zval_array_a。把数组元素0的zval命名为zval_array_0。
unset($a)之后,
数组a指向的zval的refcount减一,值为1,于是它就被扔到了root buffer中,被标记为粉色。
这就是Step A。 Step A里面的符号a,仍然假设它是一个其它变量好了。。与原来的数组a没有任何关系。
假设这个时候,root buffer满了,垃圾回收开始启动。

进入步骤B。对root buffer中的每个zval进行遍历,
假设遍历到zval_array_a了。
使用深度优先算法,寻找zval_array_a的所有元素的zval,
找到了二个zval,一个是zval_array_0,另一个是zval_array_a。
zval_array_0的refcount是2, zval_array_a的refcount是1。
对其refcount进行减一,
zval_array_0的refcount变为1, zval_array_a的refcount变为0。
最后把zval_array_a和zval_array_0标记为灰色。步骤B完成。

进入步骤C,对root buffer中的每个zval进行遍历,
假设遍历到zval_array_a了。
使用深度优先算法,寻找zval_array_a的所有元素的zval,
找到了二个zval,一个是zval_array_0,另一个是zval_array_a。
元素zval_array_a的refcount为0,标记它为白色(图上的蓝色部分),
元素zval_array_0的refcount大于0,
标记为黑色,并且让它的refcount加一。
开始采用深度优先算法,寻找zval_array_0的子元素,如果能找到就给他们搞成黑色,再把他们的refcount加一。没找到就算了。
步骤C结束。

进入步骤D,root buffer被清空。同时,白色的zval—-zval_array_a被删除掉。
垃圾回收完成。

对于一个包含环形引用的数组,对数组中包含的每个元素的zval进行减1操作,之后如果发现数组自身的zval的refcount变成了0,那么可以判断这个数组是一个垃圾。

默认情况下,php 5.3中的垃圾回收是被启用的。可以在php.ini中对其进行设置:zend.enable_gc。
关闭GC之后,php仍然会把zval扔到root buffer中,但不会对其进行分析和处理,
如果root buffer满了,新的zval将不会再进入root buffer中。

The reason why possible roots are recorded even if the mechanism has been disable is because it’s faster to record possible roots, than to have to check whether the mechanism is turned on every time a possible root could be found。

之所以在GC关闭的时候,还会把zval扔到root buffer中,是因为这种操作速度很快,
比每次refcount-1的时候都去检测GC是否开启还要快一些。

由于垃圾回收的整个过程还是比较消耗时间的,如果我们的部分程序对时间很敏感,
那么有必要在程序中实时地关闭垃圾回收,PHP的开发者也想到了这一点:
除了在php.ini中对其设置,我们还可以在PHP代码里面使用gc_enable()和gc_disable()来开启/关闭垃圾回收。
通过gc_collect_cycles()函数,可以强制进行一次垃圾回收,不管root buffer有没有满。

在gc_disable()之前,最好先去调用一下gc_collect_cycles(),
把可回收的垃圾都回收一下。

在这篇文章里面,我们讲了garbage collection 垃圾回收是怎样运作的,以及在PHP中如何控制垃圾回收,
在下一篇文章里面,我们会进行一些评测Benchmark,来看一下GC的性能如何。

使用Zend Framework中的 Zend_Pdf来创建pdf文档

  • Posted on September 6, 2010 at 3:29 pm

原文地址: http://devzone.zend.com/article/12492-Creating-PDF-Documents-with-Zend-Framework

Haru 和 PDFlib 这二个php扩展提供了完整的api来操作pdf文档。
另外还有很多开源的代码,可以操作pdf。
这篇文章主要讲解Zend Framework 中的 Zend_Pdf组件。
Zend_Pdf 是纯PHP实现的一套程序,不依赖于其它的任何外部库文件。
所以在虚拟主机上用起来是蛮合适的。
Zend_Pdf 可以对PDF进行绝大部分的操作,比如添加/删除页面,插入文件和图片,
绘图,更改PDF文档的元信息(update document meta-data)等等。

下面就以一个简单的例子开始吧:

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // define font resource
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
 
  // set font for page
  // write text to page
  $page->setFont($font, 24)
       ->drawText('That which we call a rose,', 72, 720)
       ->drawText('By any other name would smell as sweet.', 72, 620);
 
  // add page to document
  $pdf->pages[] = $page;
 
  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

程序一开始就引入了Zend auto-loader,它会自动include所需的类文件。
程序其它部分的流程就不解释了,直接看注释就可以明白了。

drawText函数的后两个参数是初始写入位置的X轴坐标和Y轴坐标。
注意:Zend_Pdf系统使用 Postscript geometry。也就是说,字体的单位是pt。1pt=1/72 inch;
(0,0)原点位于页面的左下脚。

使用Zend Framework中的 Zend_Pdf来创建pdf文档
值得提一下的是, 通过Zend_Pdf_Font既可以使用内置的14种字体,比如Zend_Pdf_Font::FONT_HELVETICA,也可以使用外部的字体文件。

看代码:

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // define font resource
  $font = Zend_Pdf_Font::fontWithPath('/tmp/comic.ttf');
 
  // set font for page
  // write text to page
  $page->setFont($font, 24)
       ->drawText('That which we call a rose,', 72, 720)
       ->drawText('By any other name would smell as sweet.', 72, 620);
 
  // add page to document
  $pdf->pages[] = $page;
 
  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

这个程序与前面的基本相同,只是通过Zend_Pdf_Font::fontWithPath来引入外部字体。
再多说二句,drawText的第四个参数,可以指定文字的字符集,Zend_Pdf会自动帮你转化成所需的字符集。省得自己用iconv了。
外部字体文件会被自动压缩,然后嵌入到pdf文件里面,所以生成出来的pdf文件会占用更多的磁盘空间。
使用Zend Framework中的 Zend_Pdf来创建pdf文档

如果想插入一张图片到pdf文件里面,只需要调用drawImage(),并提供以下参数即可:
一个Zend_Pdf_Image资源
图像左下角横坐标
图像左下角纵坐标
图像右上角横坐标
图像右上角纵坐标

暂时只支持JPEG,PNG,TIFF格式的图片。
另外:使用JPEG图片的话需要GD库。使用PNG图片的话,需要ZLIB库。
看代码吧:

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // define image resource
  $image = Zend_Pdf_Image::imageWithPath('IMG_0497.jpg');
 
  // write image to page
  $page->drawImage($image, 50, 50, 400, 400);
 
  // add page to document
  $pdf->pages[] = $page;
 
  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

使用Zend Framework中的 Zend_Pdf来创建pdf文档

上面讲的drawText有一个很大的缺点:文字太长的话,不会自动换行!
关于这一点,想必用过GD库的imagestring和imagettftext的都深有体会吧。
幸运的是,Nico Edtinger为Zend_Pdf添加了一个新的功能来解决这个问题。

http://framework.zend.com/wiki/display/ZFPROP/Zend_Pdf+text+drawing+improvements+-+Nico+Edtinger

很长的字符串会自动换行,也不会拆开某个单词,而且还可以设置左对齐或者右对齐,实在是很强大的。

看代码:

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
// define long string
$str = "Mary had a little lamb. It's fleece was white as snow. And everywhere that Mary went, the lamb was sure to go";
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // define font resource
  $font = Zend_Pdf_Font::fontWithPath('/tmp/comic.ttf');
 
  // set font
  $page->setFont($font, 14);
 
  // wrap lines of text
  // start at (10,600) and use a block of dimensions 500x500
  $page->drawTextBlock($str, 10, 600, 500, 500, Zend_Pdf_Page::ALIGN_LEFT);  
 
  // wrap lines of text
  // start at (10,500) and use a block of dimensions 200x300
  $page->setFont($font, 20);
  $page->drawTextBlock($str, 10, 500, 200, 300, Zend_Pdf_Page::ALIGN_RIGHT);  
 
  // add page to document
  $pdf->pages[] = $page;
 
  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';  
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

drawTextBlock参数的说明:

  $page->drawTextBlock($str, 10, 600, 500, 500, Zend_Pdf_Page::ALIGN_LEFT);

字符串$str,
起点横坐标10,
起点纵坐标600,
文本框的宽度500,
文本框的高度500,
选项 Zend_Pdf_Page::ALIGN_LEFT 左对齐。

使用Zend Framework中的 Zend_Pdf来创建pdf文档

此外,Zend_Pdf提供了一整套的函数来画线,画圆,以及其它图形。
来看一下画线的例子:

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // define font resource
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
 
  // set font for page
  $page->setFont($font, 10);
 
  // draw a line at the top of the page
  $page->setLineColor(new Zend_Pdf_Color_Rgb(0,0,0));
  $page->drawLine(10, 790, ($page->getWidth()-10), 790);
 
  // draw another line near the bottom of the page
  $page->drawLine(10, 25, ($page->getWidth()-10), 25);
 
  // define image resource
  $image = Zend_Pdf_Image::imageWithPath('logo.jpg');
 
  // write image to page
  $page->drawImage($image, 25, 800, ($image->getPixelWidth()+25), (800+$image->getPixelHeight()));
 
  // add footer text   
  $page->drawText('Copyright My Company 2010. All rights reserved.', ($page->getWidth()/3), 10);
 
  // add page to document
  $pdf->pages[] = $page;
 
  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

使用drawLine(),提供一下起点和终点的坐标即可画出一条线。
线条颜色可以使用Zend_Pdf_Page->setLineColor()来控制,它接受一个Zend_Pdf_Color 类型的对象做为参数。可以使用RGB,CMYK,16进制或者HTML颜色来定义Zend_Pdf_Color

使用Zend Framework中的 Zend_Pdf来创建pdf文档

再来画几个矩形框吧

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // set line color
  $page->setLineColor(new Zend_Pdf_Color_Rgb(0,0,0));
 
  // draw a face
  $page->drawRectangle(100, 300, 500, 700, Zend_Pdf_Page::SHAPE_DRAW_STROKE);
 
  // draw the left eye
  $page->setFillColor(new Zend_Pdf_Color_Rgb(255,0,0));
  $page->drawRectangle(150, 600, 200, 650);
 
  // draw the right eye
  $page->setFillColor(new Zend_Pdf_Color_Rgb(255,0,0));
  $page->drawRectangle(400, 600, 450, 650);
 
  // draw the nose
  $page->setFillColor(new Zend_Pdf_Color_Rgb(0,255,0));
  $page->drawRectangle(260, 450, 340, 550);
 
  // draw the mouth
  $page->setFillColor(new Zend_Pdf_Color_Rgb(0,0,255));
  $page->drawRectangle(200, 350, 400, 400);
 
  // add page to document
  $pdf->pages[] = $page;
 
  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

drawRectangle()函数根据提供的左下角坐标和右上角坐标,可以画一个矩形出来。
最后一个参数描述了这个矩形的样式:outline(stroked),filled or both.
thesetLineColor()可以定义矩形边框的颜色。setFillColor()可以定义矩形的填充颜色。

使用Zend Framework中的 Zend_Pdf来创建pdf文档

再来看如何画其它的形状(椭圆,多边形):

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // set line color
  $page->setLineColor(new Zend_Pdf_Color_Rgb(0,0,0));
 
  // draw a face
  $page->drawRectangle(100, 300, 500, 700, Zend_Pdf_Page::SHAPE_DRAW_STROKE);
 
  // draw the left eye
  $page->setFillColor(new Zend_Pdf_Color_Rgb(255,0,0));
  $page->drawRectangle(150, 600, 200, 650);
 
  // draw the right eye
  $page->setFillColor(new Zend_Pdf_Color_Rgb(255,0,0));
  $page->drawRectangle(400, 600, 450, 650);
 
  // draw the nose
  $page->setFillColor(new Zend_Pdf_Color_Rgb(0,255,0));
  $page->drawCircle(300, 500, 50);
 
  // draw the mouth
  $page->setFillColor(new Zend_Pdf_Color_Rgb(0,0,255));
  $page->drawEllipse(200, 350, 400, 400);
 
  // draw a hat
  $page->setFillColor(new Zend_Pdf_Color_Rgb(250,250,0));
  $page->drawPolygon(array(125,300,475), array(700,800,700));
 
  // add page to document
  $pdf->pages[] = $page;
 
  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

drawCircle()是用来画圆的,参数是圆心的坐标和半径。
drawEllipse()是用来画椭圆的,参数是此椭圆的外接(还是外切?)矩形的左下和右上角坐标。(an ellipse within a rectangular box)
drawPolygon()是用来画多边形的(n>=3)。第一个参数是所有顶点的X坐标组成的数组。第二个参数是所有顶点的Y坐标组成的数组。

使用Zend Framework中的 Zend_Pdf来创建pdf文档

再来看另外一个例子,画饼图的:

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
// define pie radius
$radius = 100;
 
// define pie values
$slices = explode(',', $_GET['data']);
 
// calculate sum of pie values
$sum = array_sum($slices);
 
// convert each slice into corresponding percentage of 360-degree circle
for ($y = 0; $y < sizeof($slices); $y++) {
  $degrees[$y] = ($slices[$y] / $sum) * 360;
}
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // set font  
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
  $page->setFont($font, 14);
 
  $lastAngle = 90;  
  for ($z=0; $z<sizeof($slices); $z++)
  {
///////////////////////////////////////////////////////////////////////////
///注:原文中的下面这一段代码不全,我自己补上的。
///////////////////////////////////////////////////////////////////////////
	  $dx = $dy = 0;
	  if (0 <= $lastAngle && $lastAngle< 90)
	  {
		  $dy = $radius * sin(deg2rad($lastAngle));
		  $dx = $radius * cos(deg2rad($lastAngle));
 
	  }
	  elseif (90 <= $lastAngle && $lastAngle< 180)
	  {
		  $dy = $radius * sin(deg2rad(180-$lastAngle));
		  $dx = -1 * $radius * cos(deg2rad(180-$lastAngle));
	  }
	  elseif (180 <= $lastAngle && $lastAngle < 270)
	  {
		  $dy = -1 * $radius * sin(deg2rad($lastAngle-180));
		  $dx = -1 * $radius * cos(deg2rad($lastAngle-180));
	  }
	  else
	  {
		  $dy = -1 * $radius * sin(deg2rad(360-$lastAngle));
		  $dx = $radius * cos(deg2rad(360-$lastAngle));
	  }
	  $endX = 250+$dx;
	  $endY = 250+$dy;
/////////////////////////////////////////////////////////////
/////////补充完毕。
/////////////////////////////////////////////////////////////
  	$page->drawLine(250, 250, $endX, $endY);
 
    // reset the last angle value
    $lastAngle = $lastAngle + $degrees[$z]; 
  }  
 
  // draw the circle outline
  $page->drawCircle(250, 250, 100, Zend_Pdf_Page::SHAPE_DRAW_STROKE);
 
  // add page to document
  $pdf->pages[] = $page;
 
  // save document
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

艾,这个饼图,不知道中间那段计算直角边长的方法有没有办法简化一些。
使用Zend Framework中的 Zend_Pdf来创建pdf文档
继续吧。

Zend_Pdf还引入了样式(Style)的概念。
所谓的样式就是预先定义好的线条颜色,填充颜色,字体之类的集合。
这个东西最大的好处就是,
当你在多种不同样式之间切换,一个函数setStyle()就搞定了,
不用每次都去重新设定线条颜色,填充颜色之类的。

下面的例子定义了二个样式:cerulean和crimson。然后进行了样式切换。

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // define a style  
  $cerulean = new Zend_Pdf_Style();
  $cerulean->setFillColor(new Zend_Pdf_Color_Rgb(0, 161, 224));
  $cerulean->setLineColor(new Zend_Pdf_Color_Rgb(0, 161, 224));
  $cerulean->setLineWidth(5);
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
  $cerulean->setFont($font, 32);  
 
  // define another style
  $crimson = new Zend_Pdf_Style();
  $crimson->setFillColor(new Zend_Pdf_Color_Rgb(194, 0, 0));
  $crimson->setLineColor(new Zend_Pdf_Color_Rgb(194, 0, 0));
  $crimson->setLineWidth(2);
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_TIMES_ROMAN);
  $crimson->setFont($font, 20);  
 
  // write text to page 
  // using different styles
  $page->setStyle($cerulean)
       ->drawText('That which we call a rose,', 72, 720);
 
  $page->setStyle($crimson)
       ->drawText('By any other name would smell as sweet.', 72, 620);
 
  // add page to document
  $pdf->pages[] = $page;
 
  // send to browser as download
  header("Content-Disposition: attachment; filename=example.pdf");
  header('Content-Type: application/pdf');
  echo $pdf->render();
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

使用Zend Framework中的 Zend_Pdf来创建pdf文档

很多时候,我们需要用table表格来表现一些数据。
But em,
Zend_Pdf 不支持直接画table。
如果我们手绘表格的话,计算量未免太大,一旦要做改动的话,也很难处理。
幸好,ursh开发了一个非官方的table组件;

http://sourceforge.net/projects/zendpdftable/

虽然是beta版的。

下载之后,扔到Zend Framework的library目录里面。
看以下的代码:

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // set up database access parameters  
  $params = array ('host'     => '127.0.0.1',
                   'username' => 'user',
                   'password' => 'pass',
                   'dbname'   => 'world');
 
  // configure adapter and query database                   
  $db = Zend_Db::factory('PDO_MYSQL', $params);
  $stmt = $db->query('SELECT Name, Code, Region FROM country LIMIT 0, 150');
 
  // create PDF
  $pdf = new My_Pdf_Document('example.pdf', '.');
 
  // create page
  $page = $pdf->createPage();
 
  // define font resource
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
 
  // set font
  $page->setFont($font, 24);
 
  // create table
  $table = new My_Pdf_Table(3);
 
  // iterate over record set
  // set up table content
  while ($record = $stmt->fetch()) {
    $row = new My_Pdf_Table_Row();
    $cols = array();
    foreach ($record as $k => $v) {
      $col = new My_Pdf_Table_Column();
      $col->setText($v);
      $cols[] = $col;      
    }
    $row->setColumns($cols);
    $row->setFont($font, 14);
    $row->setBorder(My_Pdf::TOP, new Zend_Pdf_Style());
    $row->setBorder(My_Pdf::BOTTOM, new Zend_Pdf_Style());
    $row->setBorder(My_Pdf::LEFT, new Zend_Pdf_Style());
    $row->setCellPaddings(array(10,10,10,10));
    $table->addRow($row);
  }
 
  // add table to page
  $page->addTable($table, 0, 0);
 
  // add page to document
  $pdf->addPage($page);
 
  // save as file
  $pdf->save();
  echo 'SUCCESS: Document saved!';  
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

使用Zend Framework中的 Zend_Pdf来创建pdf文档
也没什么好解释的。

为多页面的pdf文件创建书签(bookmark,目录)
就是下面的效果:
使用Zend Framework中的 Zend_Pdf来创建pdf文档

代码:

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create root node of outline tree
  $pdf->outlines[0] = Zend_Pdf_Outline::create('Table of Contents', null);
 
  // add pages to document
  // and page links to outline
  for ($x=1; $x<=10; $x++) {
    $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
    $pdf->pages[] = $page;
    $destination{$x} = Zend_Pdf_Destination_Fit::create($page);
    $pdf->setNamedDestination('page_'.$x, $destination{$x});    
    $pdf->outlines[0]->childOutlines[] = Zend_Pdf_Outline::create('Page '.$x, $pdf->getNamedDestination('page_'.$x));    
  }
 
  // save documents
  $pdf->save('example.pdf');    
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

这个也不讲了,看看代码吧。

    $destination{$x} = Zend_Pdf_Destination_Fit::create($page);
    $pdf->setNamedDestination('page_'.$x, $destination{$x});    
    $pdf->outlines[0]->childOutlines[] = Zend_Pdf_Outline::create('Page '.$x, $pdf->getNamedDestination('page_'.$x));

这里可以简化成:

    $pdf->outlines[0]->childOutlines[] = Zend_Pdf_Outline::create('Page '.$x, Zend_Pdf_Destination_Fit::create($page));

最后讲一下如何设置pdf文件的元信息(meta-data),就是下面的东西:

使用Zend Framework中的 Zend_Pdf来创建pdf文档

<?php
// include auto-loader class
require_once 'Zend/Loader/Autoloader.php';
 
// register auto-loader
$loader = Zend_Loader_Autoloader::getInstance();
 
try {
  // create PDF
  $pdf = new Zend_Pdf();
 
  // create A4 page
  $page = new Zend_Pdf_Page(Zend_Pdf_Page::SIZE_A4);
 
  // set font  
  $font = Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA);
  $page->setFont($font, 14);
 
  // write text to page 
  $page->drawText('That which we call a rose,', 72, 720)
       ->drawText('By any other name would smell as sweet.', 72, 620);
 
  // add page to document
  $pdf->pages[] = $page;
 
  // set document properties
  $pdf->properties['Author'] = 'William Shakespeare';
  $pdf->properties['Title'] = 'Romeo and Juliet';
  $pdf->properties['Subject'] = 'Act II, Scene II';
  $pdf->properties['Keywords'] = 'shakespeare, romeo, juliet, capulet, montague';
  $pdf->properties['CreationDate'] = "D:201007210634Z00'00'";
  $pdf->properties['ModDate'] = "D:201008150634Z00'00'";
 
  // save as file
  $pdf->save('example.pdf');
  echo 'SUCCESS: Document saved!';
} catch (Zend_Pdf_Exception $e) {
  die ('PDF error: ' . $e->getMessage());  
} catch (Exception $e) {
  die ('Application error: ' . $e->getMessage());    
}

只要设置Zend_Pdf的properties数组里的相关属性即可。

文章就到这里了。
其它更多的功能,比如pdf文件与用户交互啦,就靠您自己去测试了。
Zend_Pdf官方文档:

http://framework.zend.com/manual/zh/zend.pdf.html

Gentoo上apache2默认的php配置存在安全隐患,可导致任意文件被当作php文件来执行

  • Posted on September 6, 2010 at 10:18 am

原文地址: http://ilia.ws/archives/226-Beware-of-the-default-Apache-2-config-for-PHP.html

一周之前,我在升级开发服务器的时候,发现了一个php + apache2在配置上的安全隐患。
一些linux的发行版的默认配置,都存在这个问题。
在这些发行版里面(包括Gentoo。不包括CentOS,Ubuntu),apache2的默认的php配置为:

<IfModule mod_mime.c>
AddHandler application/x-httpd-php .php
AddHandler application/x-httpd-php-source .phps
</IfModule>

目的是把 file.php当作php文件来解析,
但是file.php.txt也会被当作php文件解析出来!

修改成以下的配置就OK了:

<IfModule mod_mime.c>
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
</IfModule>

discuz 新注册用户无法设置默认编辑器模式,只能以源代码方式发贴

  • Posted on September 4, 2010 at 12:41 pm

discuz 7.2

discuz 新注册用户无法设置默认编辑器模式,只能以源代码方式发贴

后台确定是设置的默认为所见即所得模式。

新注册用户登录之后, 即使在个人设置的“论坛个性化”设置里面,
设置编辑器为所见即所得,也无效。

后来看到include/post.php中:

if($prompt) {
if($taskon && ($prompt & 8)) {
$prompts['newbietask'] = 1;
$disallowfloat = str_replace('task', '', $disallowfloat);
$disallowfloat .= '|newthread|reply';
$editormode = 0;
}
如果论坛开启了任务,但是新用户并没有完成新手任务, 就设置 editormode为0,表示源代码编辑器。
ps:为2的话,表示使用论坛默认编辑器。 为1表示所见即所得编辑器.

初步学习php中的Reflection(反射)

  • Posted on September 4, 2010 at 10:53 am

原文地址:http://ctankersley.com/2010/09/03/getting-started-with-reflection/

PHP5添加了一项新的功能:Reflection。
使得程序员可以reverse-engineer class, interface,function,method and extension。
通过PHP代码,就可以得到某object的所有信息,并且可以和它交互。

本文主要讲解ReflectionClass部分。

假设有一个类Person

class Person {  
    /** 
     * For the sake of demonstration, we"re setting this private
     */ 
    private $_allowDynamicAttributes = false;
 
    /** type=primary_autoincrement */
    protected $id = 0;
 
    /** type=varchar length=255 null */
    protected $name;
 
    /** type=text null */
    protected $biography;
 
        public function getId()
        {
                return $this->id;
        }
        public function setId($v)
        {
                $this->id = $v;
        }
        public function getName()
        {
                return $this->name;
        }
        public function setName($v)
        {
                $this->name = $v;
        }
        public function getBiography()
        {
                return $this->biography;
        }
        public function setBiography($v)
        {
                $this->biography = $v;
        }
}

通过ReflectionClass,我们可以得到Person类的以下信息:
1 常量 Contants
2 属性 Property Names
3 方法 Method Names
4 静态属性 Static Properties
5 命名空间 Namespace
6 Person类是否为final或者abstract

只要把类名”Person”传递给ReflectionClass就可以了

$class = new ReflectionClass('Person');

获取属性(Properties)

$properties = $class->getProperties();
foreach($properties as $property) {
    echo $property->getName()."\n";
}
// 输出:
// _allowDynamicAttributes
// id
// name
// biography

默认情况下,ReflectionClass会获取到所有的属性,private 和 protected的也可以。
如果只想获取到private属性,就要额外传个参数:

$private_properties = $class->getProperties(ReflectionProperty::IS_PRIVATE);

可用参数列表:
ReflectionProperty::IS_STATIC
ReflectionProperty::IS_PUBLIC
ReflectionProperty::IS_PROTECTED
ReflectionProperty::IS_PRIVATE
如果要同时获取public 和private 属性,就这样写:
ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED
应该不会感觉陌生吧。

继续。
通过$property->getName()可以得到属性名,通过getDocComment可以得到写给property的注释。

foreach($properties as $property) {
    if($property->isProtected()) {
        $docblock = $property->getDocComment();
        preg_match('/ type\=([a-z_]*) /', $property->getDocComment(), $matches);
        echo $matches[1]."\n";
    }
}
// Output:
// primary_autoincrement
// varchar
// text

有点不可思议了吧。竟然连注释都可以取到。

获取方法(methods):
通过getMethods() 来获取到类的所有methods。返回的是ReflectionMethod对象的数组。不再演示。

最后通过ReflectionMethod来调用类里面的method.

$data = array("id" => 1, "name" => "Chris", "biography" => "I am am a PHP developer");
foreach($data as $key => $value) {
    if(!$class->hasProperty($key)) {
        throw new Exception($key." is not a valid property");
    }
 
    if(!$class->hasMethod("get".ucfirst($key))) {
        throw new Exception($key." is missing a getter");
    }
 
    if(!$class->hasMethod("set".ucfirst($key))) {
        throw new Exception($key." is missing a setter");
    }
 
    // Make a new object to interact with
    $object = new Person();
 
    // Get the getter method and invoke it with the value in our data array
    $setter = $class->getMethod("set".ucfirst($key));
    $ok = $setter->invoke($object, $value);
 
    // Get the setter method and invoke it
    $setter = $class->getMethod("get".ucfirst($key));
    $objValue = $setter->invoke($object);
 
    // Now compare
    if($value == $objValue) {
        echo "Getter or Setter has modified the data.\n";
    } else {
        echo "Getter and Setter does not modify the data.\n";
   }
}

有点意思吧。

不过,感觉起来不太实用?
那么来看这个实用的程序吧:PhpORM_Cli_GenerateSql
http://github.com/dragonmantank/PhpORM/blob/master/PhpORM/Cli/GenerateSql.php ,
作者使用Reflection来构造”CREATE TABLE”的sql语句。

PHP垃圾回收机制 之 变量的处理

  • Posted on September 3, 2010 at 3:52 pm

原文地址: http://derickrethans.nl/collecting-garbage-phps-take-on-variables.html

关于PHP垃圾回收机制(Garbage Collection . GC) ,原作者写了三篇文章。这是第一篇,主要讲解PHP如何处理变量。
第二篇和第三篇主要讲常用的GC方法,以及GC是如何实现的,以及一些其它的说明和评测。

PHP版本:5.3

进入正题:

PHP把变量保存在zval容器里面。容器,container,可以想像成一块存储区域,或者一个盒子。
PHP垃圾回收机制 之 变量的处理
如上图所示,zval容器保存了此变量的类型type,值value,还有其它二块东西。
其中一个叫做”is_ref”, 它是一个bool型的值,占用一个bit,表示该zval容器(也就是这个变量)是否被引用。
php就是使用这个bit来判断变量是个普通变量,或者是个引用(reference)。
说起来,PHP有二种引用:
其一是用户代码中的&
其二是PHP内部实现的引用计数机制(internal reference counting mechanism),用来优化内存使用的。
is_ref是指&

zval容器中的另外一部分,是”refcount”,引用计数。用来记录有多少个变量指向这个zval容器。
通常吧,这个时候我们只说symbol(符号),不说variables(变量),其实是同一个意思。
当refcount为1的时候,is_ref必须为0。
当refcount为0的时候,该容器会被删除掉,释放空间出来。

所有的symbol保存在一张symbol table里面(符号表)。
php维护了很多张这样的表,GLOBAL一张,每个函数一张。类的每个方法也有一张。
基本上是按照变量的作用域(scope)来建表的。

当我们用一个constant value(常量)来为变量赋值的时候,zval容器才会被创建出来。
举例:

$a = "this is";

PHP垃圾回收机制 之 变量的处理
如上图所示;在当前的作用域里面,符号a被创建出来,zval容器也被创建,
类型:string
值: this is
is_ref: 默认为0
refcount: 1 表示现在只有一个符号(a)指向这个容器。

如果你安装了Xdebug,可以使用如下的代码:

xdebug_debug_zval("a");

可以看到:

a: (refcount=1, is_ref=0)="this is"

貌似看不到变量类型嘛。

再看下面的例子:

$a = "this is";
$b = $a;
$c = $a;
xdebug_debug_zval("a");

可以看到:

a: (refcount=3, is_ref=0)="this is"

PHP垃圾回收机制 之 变量的处理
如上图所示, refcount 变成3了,表示有3个符号指向这个容器。
看来PHP还是蛮聪明的,它并没有给$b创建一个单独的容器出来,节省了内存。
只有到了必要的时候,才会创建新的容器。
如下:

$a = "this is";
$b = $a;
$c = $a;
$c = 42;

PHP垃圾回收机制 之 变量的处理
如上图所示, 在$c = 42的时候,一个新的容器被创建出来,原有容器的refcount减一。

如果我们unset一个变量呢?

$a = "this is";
$c = $b = $a;
xdebug_debug_zval("a");
$c = 42;
unset( $b, $c );
xdebug_debug_zval("a");

会显示:

a: (refcount=3, is_ref=0)="this is"
a: (refcount=1, is_ref=0)="this is"

PHP垃圾回收机制 之 变量的处理
如果我们再调用 unset($a),那么这个zval容器也就一起消失了。

接下来讲引用赋值:

$a = "this is"
$b = &$a;
$c = &$b;

PHP垃圾回收机制 之 变量的处理
如图:符号a b和c都指向同一个容器,而且这个容器的is_ref位为1(因为&出现了)。
表示这是一个引用。refcount为3。
接下来

$b = 42;

PHP垃圾回收机制 之 变量的处理
会把容器的值设置为42。

接下来unset一个变量

unset($c);

PHP垃圾回收机制 之 变量的处理
容器的refcount减一,c这个符号消失。

再unset($a)呢?
PHP垃圾回收机制 之 变量的处理
refcount继续减一,值为1。同时, is_ref变回0。引用消失。a符号消失。

您理解了吗?

引用赋值与普通赋值的区别就在于容器的is_ref位为1还是为0。
如果为1,改变任意一个变量的值,只是会更改容器的值。
如果为0,改变任意一个变量的值,都会创建一个新的容器出来。

如果把引用赋值与普通赋值混合起来会怎么样?

$a = "this is";
$b = $a;

PHP垃圾回收机制 之 变量的处理
如图,二个符号都指向同一个容器。

继续

$c = &$b;

PHP垃圾回收机制 之 变量的处理
如图,一个新的容器被分配出来,b和c都指向它,而且is_ref=1。
没什么特殊的。想想也应该是这样。
总不可能abc都指向同一个容器吧。

这样呢?

$a = "this is";
$b = &$a;

PHP垃圾回收机制 之 变量的处理
如图,二个符号指向同一容器,is_ref=1,因为发生了引用赋值。

继续

$c = $a;

PHP垃圾回收机制 之 变量的处理
如图,一个新的容器被分配出来,c指向了它。 原容器没发生变化。
看到这里,是不是有点乱了。慢慢体会一下吧。

休息一下。

接下来讲PHP如何处理数组和对象。

以数组为例吧,对象也差不多。
每个数组都维护了一张自己的符号表,保存了自己的元素。
看代码:

$a = array( "meaning" => "life", "number" => 42 );
xdebug_debug_zval( "a" );

会显示:

a: (refcount=1, is_ref=0)=array (
        "meaning" => (refcount=1, is_ref=0)="life",
        "number" => (refcount=1, is_ref=0)=42
)

很合理,也很合逻辑。
PHP垃圾回收机制 之 变量的处理
如图所示,三个zval容器被创建了出来,”a”, “meaning”, “number”。
数组对refcount的处理,与普通的变量(标量 scalar)是相同的。演示一下看:

$a = array( "meaning" => "life", "number" => 42 );
$a["life"] = $a["meaning"];
xdebug_debug_zval( "a" );

会显示:

a: (refcount=1, is_ref=0)=array (
        "meaning" => (refcount=2, is_ref=0)="life",
        "number" => (refcount=1, is_ref=0)=42,
        "life" => (refcount=2, is_ref=0)="life"
)

PHP垃圾回收机制 之 变量的处理
如图,life和meaning都指向同一个容器,容器的refcount为2。
和前面所讲的php处理标量的行为是完全一致的。

unset数组的一个元素,与unset一个普通变量的情况也是完全一致的,
删除这个符号,然后refcount减一,如果值为0了,就删除这个容器。不再举例。

最后来看一个奇怪的操作:
我们把数组自身作为它自己的元素,并且使用引用赋值。
好象说得不太明白,看代码:

$a = array( "one" );
$a[] =& $a;
xdebug_debug_zval( "a" );

如果不是引用赋值,就简单多了。不表。

输出为:

a: (refcount=2, is_ref=1)=array (
        0 => (refcount=1, is_ref=0)="one",
        1 => (refcount=2, is_ref=1)=...
)

PHP垃圾回收机制 之 变量的处理
需要解释吗?
应该也算是蛮合理的吧。

如果unset a变量,会怎么样?
按照以前的经验,应该是会删除a这个符号,然后refcount减一。如果值为0了,就清理掉容器。
不过从直觉上来讲,$a被unset,那么这个数组也应该消失才对吧?

unset($a);

输出:

(refcount=1, is_ref=1)=array (
        0 => (refcount=1, is_ref=0)="one",
        1 => (refcount=1, is_ref=1)=...
)

虽然在当前的作用域里面,已经没有符号指向这个zval容器,但它并没有被释放出来,
只是因为array[1]还指向着它?
PHP垃圾回收机制 之 变量的处理
因为没有变量指向这个容器了,所以在php代码中,我们无法对其进行任何操作。
这就出现了内存泄漏(memory leak)。
不过,在脚本执行结束之后,php会把这块区域也释放出来的。

所以,使用引用的时候,一定要小心。

如果你想继续研究,php如何处理function参数,function局部变量,以及更多的引用赋值,请看这个PDF:

http://derickrethans.nl/files/phparch-php-variables-article.pdf

不过要注意,他讲的是php4.3。不知道在5.3的时候,会不会有什么变化。
我就不再翻译了。

使用php PHPMailer SMTP Gmail来发送邮件

  • Posted on September 3, 2010 at 11:42 am

原文地址:http://www.web-development-blog.com/archives/send-e-mail-messages-via-smtp-with-phpmailer-and-gmail/

为什么要使用Gmail来发送邮件呢?
首先它是免费的,每天大概可以发送500封邮件。
关于这个数量限制,可以来这里看看:http://mail.google.com/support/bin/answer.py?hl=en&answer=22839
其次您网站所在的服务器IP很有可能已经在垃圾邮件的黑名单里面了。

操作步骤:
1 注册Gmail帐号
2 下载最新版本的PHPMailer http://sourceforge.net/projects/phpmailer/files/phpmailer%20for%20php5_6/
3 检查服务器防火墙是否允许你连接其它服务器的465端口
4 包含phpmailer的class:

require_once('phpmailer/class.phpmailer.php');

5 设置用户名密码:

define('GUSER', 'you@gmail.com'); // Gmail username
define('GPWD', 'password'); // Gmail password

6 发送邮件的函数:

function smtpmailer($to, $from, $from_name, $subject, $body) { 
	global $error;
	$mail = new PHPMailer();  // create a new object
	$mail->IsSMTP(); // enable SMTP
	$mail->SMTPDebug = 0;  // debugging: 1 = errors and messages, 2 = messages only
	$mail->SMTPAuth = true;  // authentication enabled
	$mail->SMTPSecure = 'ssl'; // secure transfer enabled REQUIRED for Gmail
	$mail->Host = 'smtp.gmail.com';
	$mail->Port = 465; 
	$mail->Username = GUSER;  
	$mail->Password = GPWD;           
	$mail->SetFrom($from, $from_name);
	$mail->Subject = $subject;
	$mail->Body = $body;
	$mail->AddAddress($to);
	if(!$mail->Send()) {
		$error = 'Mail error: '.$mail->ErrorInfo; 
		return false;
	} else {
		$error = 'Message sent!';
		return true;
	}
}

另外为了保证邮件发送的成功率,最好是设置一下您域名的SPF记录。
各大网站都设置了呢。

163.com.                14836   IN      TXT     "v=spf1 include:spf.163.com -all"
qq.com.                 2614    IN      TXT     "v=spf1 ip4:119.147.10.0/23 ip4:119.147.18.0/24 ip4:222.202.96.0/24 ip4:64.71.138.0/25 ip4:58.251.149.0/24 ip4:119.147.6.0/24 ip4:119.147.8.0/24 ip4:112.90.137.0/22 ip4:119.147.14.0/24 ip4:183.60.2.0/24 ip4:113.108.77.0/24 ip4:183.62.126.0/24 ~all"
sina.com.               60      IN      TXT     "v=spf1 include:spf.sinamail.sina.com.cn -all"

关于SPF可以在这里看一下说明:

http://www.google.com/support/a/bin/answer.py?hl=en&answer=33786

使用php来压缩JavaScript代码

  • Posted on September 2, 2010 at 4:47 pm

原文地址:

http://www.sitepoint.com/blogs/2010/08/30/compress-javascript-closure-compiler-rest-api/

以及:

http://www.sitepoint.com/blogs/2010/08/31/compress-javascript-with-php/

随着ajax的盛行,我们网站要调用的JavaScript代码越来越多,
虽然客户端和服务端的带宽都在不断地提升,
PS:最近杭州电信有个说法是“光城市”,光纤覆盖的城市
但我们还是应该优化一下JavaScript代码。
一般来说,有三种优化的方式:
1 把

<script>

标签放到

</body>

之后,这样网站内容可以尽快显示出来。
2 把多个JS文件合并成一个。有效减少HTTP请求次数。
3 压缩JS文件,去掉其中的空格,空行,缩短变量名,以及其它的优化方式。

虽然网络上已经有N多的压缩软件,或者在线压缩程序,可以帮助我们压缩JS,
我经常使用的是GOOGLE提供的Closure Compiler在线压缩,
网址是: http://closure-compiler.appspot.com/home
但这通常是一个手工的过程。
对于大多数开发者来说,
在开发环境中,要修改未压缩的JS文件,
开发完毕之后,手工对其进行压缩,
再发布到生产环境中。

有没有自动的办法呢?
GOOGLE的Closure Compiler终于提供REST API了,可以用PHP来调用它了。
我们想做到:
1 压缩之后,生成错误报告
2 发布未压缩版本的JS文件到测试和生产环境中
3 测试人员和普通用户要访问到压缩之后的JS代码。
4 版本控制机制
5 自动进行压缩

假设我们的网站最后调用三个JS文件

<script src="script/file1.js"></script>  
<script src="script/file2.js"></script>  
<script src="script/file3.js"></script>

首先我们需要把这段代码替换成:

<script src="script/?file1&file2&file3"></script>

然后在script目录中创建一个index.php

<?php  
// fetch JavaScript files to compress  
// 得到JS文件名
$jsfiles = array_keys($_GET);  
$js = '';       // code to compress  
$jscomp = '';           // compressed JS  
$err = '';          // error string
// fetch JavaScript files  
// 读取所有的JS文件内容,并且合并到一起。
// 如果出错,就记录下文件名。
for ($i = 0, $j = count($jsfiles); $i < $j; $i++)   
{  
  $fn = $jsfiles[$i] . '.js';  
  $jscode = @file_get_contents($fn);  
  if ($jscode !== false) {  
    $js .= $jscode . "\n";  
  }  
  else {  
    $err .= $fn . '; ';  
  }  
}
if ($err != '') {  
  // error: missing files  
  // 如果出错的话,就返回如下的JS。提醒开发者。
  $jscomp = "alert('The following JavaScript files   
could not be read:\\n$err');";  
}

如果没有出错:
$apiArgs 数组存放的是Closure Compiler API Options。
更多options请见: http://code.google.com/closure/compiler/docs/api-ref.html

else if ($js != '') {  
  // REST API arguments  
  $apiArgs = array(  
    'compilation_level'=>'ADVANCED_OPTIMIZATIONS',   //优化度
    'output_format' => 'text',  		     
    'output_info' => 'compiled_code'  
   );  
	//$js 就是所有的JS代码的合体
   $args = 'js_code=' . urlencode($js);  
	//把它们拼到一起,一定很长。
   foreach ($apiArgs as $key => $value) {  
     $args .= '&' . $key .'='. urlencode($value);  
   }

接下来就可以去调用REST API了,还是继续用CURL吧:

// API call using cURL  
  $call = curl_init();  
  curl_setopt_array($call, array(  
    CURLOPT_URL =>   
'http://closure-compiler.appspot.com/compile',  
    CURLOPT_POST => 1,  
    CURLOPT_POSTFIELDS => $args,  
    CURLOPT_RETURNTRANSFER => 1,  
    CURLOPT_HEADER => 0,  
    CURLOPT_FOLLOWLOCATION => 0  
  ));  
	//$jscomp 就是返回的压缩之后的JS代码
  $jscomp = curl_exec($call);  
  curl_close($call);

最后输出:

} //end of if-else 
 
// output content   
header('Content-type: text/javascript');   
echo $jscomp;   
?>

完整代码下载地址:http://blogs.sitepointstatic.com/examples/tech/jscompress/php-js-compress.zip

但是这还没有完。
如果仅仅是这样, 用户每次访问的时候,都会去调用一下这个REST API,速度可想而知了。
后续的完善工作:
1 错误处理: 需要检查API调用的返回值,以及压缩过程中,Closure Compiler生成的错误报告。
2 服务端缓存: 压缩一次之后,写入缓存文件。
3 客户端缓存: 设置HTTP Expire headers。 这样客户端直接缓存住了。
4 js的URL后面加上版本号:

<script src="script/?file1&file2&file3=1.1.11"></script>

以便在JS更新之后,可以立即更新客户端缓存。

PHP和couchDB

  • Posted on September 2, 2010 at 2:52 pm

原文网址:http://gonzalo123.wordpress.com/2010/03/15/php-and-couchdb/

最近一直在研究couchDB。有很多API或者PHP扩展都可以帮助你使用couchDB。
在我的上一篇文章里面,已经介绍了如何用php和curl来向couchDB发送请求
只要实现REST的HTTP请求,就可以很简单地操作couchDB。

不过呢,我最熟悉的还是关系型数据库,比如mysql啦。SQL语句写得也是刚刚的。
为什么在couchDB上就不能使用SELECT, INSERT, UPDATE和DELETE语句呢?
因为它是NoSQL公司的产品么?

那么我们再把上个版本里面的class封装一下吧:
在这里说明一下,原作者已经把上个版本里面的Http类改良过了,现在叫做Nov_Http,增加了一些新功能。
写一个新的class Nov_CouchDb。它的主要精力放在 HTTP Document API 上面。
关于HTTP Document API 可以看这里的文章:

http://wiki.apache.org/couchdb/HTTP_Document_API

假设couchDB监听在localhost:5984,然后去 http://localhost:5984/_utils/ 创建一个”users”数据库(这一段自己操作就行了)。

继续TDD。
INSERT

$couchDb = new Nov_CouchDb('localhost', 5984);
$couchDb->db('users')->insert('gonzalo', array('password' => "g1"));

SELECT

$data = $couchDb->db('users')->select('gonzalo')->asArray();
print_r($data);

UPDATE

$couchDb->db('users')->update('gonzalo', array('password' => 'g2'));

DELETE

$out = $couchDb->db('users')->delete('gonzalo')->asArray();
print_r($out);

首先来看 Nov_CouchDb 的构造函数:

class Nov_CouchDb
{
    private $_protocol;
    private $_host;
    private $_port;
    private $_user;
    private $_password;
 
    public function __construct($host, $port=Nov_Http::DEF_PORT , $protocol=Nov_Http::HTTP, $user = null, $password=null)
    {
        $this->_host     = $host;
        $this->_port     = $port;
        $this->_protocol = $protocol;
        $this->_user     = $user;
        $this->_password = $password;
    }
}

其实没什么好看的,保存了一下配置信息而已,并没有进行连接。

还有一个public 函数 db()

    private $_db;
    /**
     * @param string $db
     * @return Nov_CouchDb
     */
    public function db($db)
    {
        $this->_db = $db;
        return $this;
    }

它保存了数据库名称,并且返回了对象本身。

好,下面来看我是如何实现SELECT INSERT DELETE UPDATE的.

SELECT

public function select($key)
{
    try {
        $out = Nov_Http::connect($this->_host, $this->_port, $this->_protocol)
            ->setCredentials($this->_user, $this->_password)
            ->doGet("{$this->_db}/{$key}");
    } catch (Nov_Http_Exception $e) {
        $this->_manageExceptions($e);
    }
    return new Nov_CouchDb_Resulset($out);
}

INSERT

/**
 * @param string $key
 * @param array $values
 * @return Nov_CouchDb_Resulset
 */
public function insert($key, $values)
{
    try {
        $out = Nov_Http::connect($this->_host, $this->_port, $this->_protocol)
            ->setCredentials($this->_user, $this->_password)
            ->setHeaders(array('Content-Type' =>  'application/json'))
            ->doPut("{$this->_db}/{$key}", json_encode($values));
    } catch (Nov_Http_Exception $e) {
        $this->_manageExceptions($e);
    }
    return new Nov_CouchDb_Resulset($out);
}

UPDATE

/**
 * @param string $key
 * @param array $values
 * @return Nov_CouchDb_Resulset
 */
public function update($key, $values)
{
    try {
        $http = Nov_Http::connect($this->_host, $this->_port, $this->_protocol)
            ->setCredentials($this->_user, $this->_password);
        $out = $http->doGet("{$this->_db}/{$key}");
        $reg = json_decode($out);
        $out = $http->setHeaders(array('Content-Type' =>  'application/json'))
            ->doPut("{$this->_db}/{$key}", json_encode($values));
    } catch (Nov_Http_Exception $e) {
        $this->_manageExceptions($e);
    }
    return new Nov_CouchDb_Resulset($out);
}

DELETE:

/**
 * @param string $key
 * @return Nov_CouchDb_Resulset
 */
public function delete($key)
{
    try {
        $http = Nov_Http::connect($this->_host, $this->_port, $this->_protocol)
            ->setCredentials($this->_user, $this->_password);
        $out = $http->doGet("{$this->_db}/{$key}");
        $reg = json_decode($out);
        $out = $http->doDelete("{$this->_db}/{$key}", array('rev' => $reg->_rev));
    } catch (Nov_Http_Exception $e) {
        $this->_manageExceptions($e);
    }
    return new Nov_CouchDb_Resulset($out);
}

这四类操作,都返回了一个 Nov_CouchDb_Resulset 对象。
为什么要这样做呢? 直接把 couchDB 的返回值获取到不就行了么?
因为couchDB 返回的是json_encode过的字符串。
有时候我们需要php数组,或者是个object。
所以我创建了Nov_CouchDb_Resulset类来对输出进行转化:

class Nov_CouchDb_Resulset
{
    private $_data;
 
    function __construct($data)
    {
        $this->_data = $data;
    }
 
    function asArray()
    {
        return (array) json_decode($this->_data);
    }
 
    function asJson()
    {
        return $this->_data;
    }
 
    function asObject()
    {
        return json_decode($this->_data);
    }
}

如何使用:

// Different outputs
$data = $couchDb->db('users')->select('dummy')->asArray();
print_r($data);
 
$data = $couchDb->db('users')->select('dummy')->asObject();
print_r($data);
 
$data = $couchDb->db('users')->select('dummy')->asJson();
print_r($data);

最后再来处理一下异常:

自己看代码吧。
代码一:

http://code.google.com/p/nov-couchdb/source/browse/Nov_Http.php?spec=svnf0a3289c07fbaf5cf7997f44ac0219961e4505bd&r=f0a3289c07fbaf5cf7997f44ac0219961e4505bd

代码二: http://code.google.com/p/nov-couchdb/source/browse/Nov_CouchDb.php

使用PHP实现REST的HTTP客户端

  • Posted on September 2, 2010 at 1:59 pm

原文网址: http://gonzalo123.wordpress.com/2010/01/09/building-a-simple-http-client-with-php-a-rest-client/
REST webservices are cool!

近来出现了很多REST的API,配合JSONP一起,很容易开发出结构清晰的应用。
我们最近在研究CouchDB。 它有一整套的REST API。
人们已经开发了很多php库,甚至PHP扩展来调用CouchDB。
我最喜欢Zend Framework里面的REST client。
但是为了练习一下,我决定自己写一个HTTP client。
我的目的是:创建一个简单的class,可以向远程服务器发送GET ,POST, DELETE请求。

虽然还没有开始进行设计与编码,但我想,我的class应该这样被调用:

echo Http::connect('localhost', 8082)
    ->doGet('tests/httpclient/dummy.php', array('a' => "a a a"));

PS:这应该算是TDD吧,测试驱动开发。

首先我要实现connect函数,在这个函数里面我不想真的去连接服务器。
应该是在doGet的时候再去连接比较合适。
就像lftp一样,当你在命令行输入

[root@super tmp]# lftp 192.168.0.100
lftp 192.168.0.100:~>user uuu  ppp

这时其实没有进行真的连接。
只有在你上传/下载/列表文件的时候,才会进行第一次连接。

回到正题,我把connect作成了一个factory函数,它只是保存一下配置信息,
然后返回一个本类的实例。

private $_host = null;
private $_port = null;
private $_user = null;
private $_pass = null;
 
/**
 * Factory of the class. Lazy connect
 *
 * @param string $host
 * @param integer $port
 * @param string $user
 * @param string $pass
 * @return Http
 */
static public function connect($host, $port, $user=null, $pass=null)
{
    return new self($host, $port, $user, $pass);
}
protected function __construct($host, $port, $user, $pass)
{
        $this->_host = $host;
        $this->_port = $port;
        $this->_user = $user;
        $this->_pass = $pass;
}

下面为每种操作方式写函数(doGet,doPost,doDelete)

const POST   = 'POST';
const GET    = 'GET';
const DELETE = 'DELETE';
 
/**
 * POST request
 *
 * @param string $url
 * @param array $params
 * @return string
 */
public function doPost($url, $params=array())
{
    return $this->_exec(self::POST, $this->_url($url), $params);
}
 
/**
 * GET Request
 *
 * @param string $url
 * @param array $params
 * @return string
 */
public function doGet($url, $params=array())
{
    return $this->_exec(self::GET, $this->_url($url), $params);
}
 
/**
 * DELETE Request
 *
 * @param string $url
 * @param array $params
 * @return string
 */
public function doDelete($url, $params=array())
{
    return $this->_exec(self::DELETE, $this->_url($url), $params);
}

它们只是去调用了 _exec()函数。

再加入一个设置Header的函数:

private $_headers = array();
/**
 * setHeaders
 *
 * @param array $headers
 * @return Http
 */
public function setHeaders($headers)
{
    $this->_headers = $headers;
    return $this;
}

每个函数都返回了这个类的实例,是因为我比较喜欢链式写法。
就像下面这样:

echo Http::connect('localhost', 8082)
    ->setHeaders($myCustomHeaders)
    ->doGet('tests/httpclient/dummy.php', array('a' => "a a a"));

最后来看 _exec()函数。在PHP里面,有很多方法来实现它的功能,
在这里我用了CURL。 请看一下phpinfo(),确认你的php支持不支持curl。
不支持的时候,您可以使用socket函数。

const HTTP_OK = 200;
const HTTP_CREATED = 201;
const HTTP_ACEPTED = 202;
 
/**
 * Performing the real request
 *
 * @param string $type
 * @param string $url
 * @param array $params
 * @return string
 */
private function _exec($type, $url, $params = array())
{
    $headers = $this->_headers;
    $s = curl_init();
 
    if(!is_null($this->_user)){
       curl_setopt($s, CURLOPT_USERPWD, $this->_user.':'.$this->_pass);
    }
 
    switch ($type) {
        case self::DELETE:
            curl_setopt($s, CURLOPT_URL, $url . '?' . http_build_query($params));
            curl_setopt($s, CURLOPT_CUSTOMREQUEST, self::DELETE);
            break;
        case self::POST:
            curl_setopt($s, CURLOPT_URL, $url);
            curl_setopt($s, CURLOPT_POST, true);
            curl_setopt($s, CURLOPT_POSTFIELDS, $params);
            break;
        case self::GET:
            curl_setopt($s, CURLOPT_URL, $url . '?' . http_build_query($params));
            break;
    }
 
    curl_setopt($s, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($s, CURLOPT_HTTPHEADER, $headers);
    $_out = curl_exec($s);
    $status = curl_getinfo($s, CURLINFO_HTTP_CODE);
    curl_close($s);
    switch ($status) {
        case self::HTTP_OK:
        case self::HTTP_CREATED:
        case self::HTTP_ACEPTED:
            $out = $_out;
	break;
        default:
            throw new Http_Exception("http error: {$status}", $status);
    }
    return $out;
}

恩,这里使用了一个 Http_Exception 的异常处理类。

class Http_Exception extends Exception{
    const NOT_MODIFIED = 304;
    const BAD_REQUEST = 400;
    const NOT_FOUND = 404;
    const NOT_ALOWED = 405;
    const CONFLICT = 409;
    const PRECONDITION_FAILED = 412;
    const INTERNAL_ERROR = 500;
}

在向远程CouchDB服务器请求的过程中,如果出错,CouchDB会返回标准的HTTP STATUS CODE。所以上面的状态码,您应该是蛮熟悉的才对。

异常捕捉代码:

try {
   echo Http::connect('localhost', 8082)
        ->doGet('tests/couchdb/a.php', array('a' => "a a a"));
} catch (Http_Exception $e) {
    switch ($e->getCode()) {
        case Http_Exception::INTERNAL_ERROR:
            // do something
            break;
    }
}

完整代码下载地址:http://bitbucket.org/gonzalo123/gam_http/src/016c8eb81dd1/http.php

Top