<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>潘锦的空间 &#187; PHP源码阅读笔记</title>
	<atom:link href="https://www.phppan.com/tag/php%e6%ba%90%e7%a0%81%e9%98%85%e8%af%bb%e7%ac%94%e8%ae%b0/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.phppan.com</link>
	<description>SaaS SaaS架构 团队管理 技术管理 技术架构 PHP 内核 扩展 项目管理</description>
	<lastBuildDate>Sat, 04 Apr 2026 01:19:58 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=3.9.40</generator>
	<item>
		<title>PHP的压缩函数实现：gzencode、gzdeflate和gzcompress</title>
		<link>https://www.phppan.com/2014/03/php-compress/</link>
		<comments>https://www.phppan.com/2014/03/php-compress/#comments</comments>
		<pubDate>Sun, 16 Mar 2014 08:55:19 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>
		<category><![CDATA[压缩函数]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1902</guid>
		<description><![CDATA[gzencode 默认使用ZLIB_ENCODING_GZIP编码，使用gzip压缩格式，实际上是使用defa [&#8230;]]]></description>
				<content:encoded><![CDATA[<ul>
<li>
gzencode 默认使用ZLIB_ENCODING_GZIP编码，使用gzip压缩格式，实际上是使用defalte 算法压缩数据，然后加上文件头和adler32校验</li>
<li>
gzdeflate  默认使用ZLIB_ENCODING_RAW编码方式，使用deflate数据压缩算法，实际上是先用 LZ77 压缩，然后用霍夫曼编码压缩</li>
<li>
gzcompress ；默认使用ZLIB_ENCODING_DEFLATE编码，使用zlib压缩格式，实际上是用 deflate 压缩数据，然后加上 zlib 头和 CRC 校验</li>
<p><strong>这三个函数的比较实质上是三种压缩方法：deflate, zlib, gzip的比较。</strong><br />
从性能的维度看：deflate 好于 gzip 好于 zlib<br />
从文本文件默认压缩率压缩后体积的维度看：deflate 好于 zlib 好于 gzip</p>
<p>这三种算法中gzip 、zlib的作者都是Jean-Loup Gailly和 Mark Adler。<br />
这两种算法以及图形格式png，使用的压缩算法却都是deflate算法。<br />
deflate算法是同时使用了LZ77算法与哈夫曼编码（Huffman Coding）的一个无损数据压缩算法。<br />
它最初是由Phil Katz为他的PKZIP归档工具第二版所定义的，后来定义在 RFC 1951规范中。</p>
<p>deflate算法的压缩与解压的实现过程可以在压缩库zlib上找到。<br />
PHP的压缩实现依赖于zlib，zlib是一个提供了 deflate, zlib, gzip 压缩方法的函数库。<br />
我们所使用的上面三个函数，将参数中的encoding转为相同，压缩率设置相同，则其最终调用的是同一个函数，效果和性能一样。</p>
<p>PHP的zlib实现是以扩展的方式存在于ext/zlib目录中。通过deflateInit2() + deflate() + deflateEnd()三个函数配合完成压缩功能，inflateInit2() + inflate() + inflateEnd()三个函数配合完成解压功能。压缩最终都是通过php_zlib_encode函数实现调用，除了输入的字符串，压缩率，结果的输出外，不同的入口函数调用参数不同的是其encoding。deflateInit2的第四个参数指定encoding，PHP定义了三个常量：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;"> <span style="color: #339933;">#define PHP_ZLIB_ENCODING_RAW          -0xf      //deflate -15</span>
<span style="color: #339933;">#define PHP_ZLIB_ENCODING_GZIP          0x1f      //gzip 15 + 16</span>
<span style="color: #339933;">#define PHP_ZLIB_ENCODING_DEFLATE     0x0f      // zlib 15</span></pre></td></tr></table></div>

<p>三个函数在调用过程可以直接指定encoding使用其它的算法：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="shell" style="font-family:monospace;">zlib:   ZLIB_ENCODING_DEFLATE 
gzip: ZLIB_ENCODING_GZIP
deflate: ZLIB_ENCODING_RAW</pre></td></tr></table></div>

<p>此三个函数是三种算法的简单调用方式，以更好的命名展现。三个函数间可以通过指定相同的encoding达到相同的效果，并且PHP也提供zlib_encode函数作为通用的压缩函数。</p>
<p>参考资料：</p>
<p>http://www.gzip.org/zlib/rfc-deflate.html</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2014/03/php-compress/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>foreach的指针问题</title>
		<link>https://www.phppan.com/2013/04/foreach-exception/</link>
		<comments>https://www.phppan.com/2013/04/foreach-exception/#comments</comments>
		<pubDate>Sun, 14 Apr 2013 23:46:51 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[foreach]]></category>
		<category><![CDATA[PHP源码]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1821</guid>
		<description><![CDATA[在PHP中，foreach 语法结构提供了遍历数组的简单方式。 foreach 仅能够应用于数组和对象，如果尝 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>在PHP中，foreach 语法结构提供了遍历数组的简单方式。 foreach 仅能够应用于数组和对象，如果尝试应用于其他数据类型的变量，或者未初始化的变量，将导致错误。 foreach每次循环时，当前单元的值被赋给 $value 并且数组内部的指针向前移一步（因此下一次循环中将会得到下一个单元）。</p>
<p>但是手册中提醒我们：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="shell" style="font-family:monospace;">Note:
当 foreach 开始执行时，数组内部的指针会自动指向第一个单元。这意味着不需要在 foreach 循环之前调用 reset()。
在循环中修改 foreach 依赖其内部数组指针将可能导致意外的行为。</pre></td></tr></table></div>

<p>这里我们所要说的是foreach可能导致的意外情况。如代码1示例：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
<span style="color: #000088;">$arr</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">2</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">3</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">4</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">5</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #b1b100;">foreach</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$key</span> <span style="color: #339933;">=&gt;</span> <span style="color: #339933;">&amp;</span><span style="color: #000088;">$row</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
<span style="color: #b1b100;">echo</span> <span style="color: #990000;">key</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'=&gt;'</span><span style="color: #339933;">,</span> <span style="color: #990000;">current</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>会输出什么？</p>
<p>如代码2示例呢？</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
<span style="color: #000088;">$arr</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">2</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">3</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">4</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">5</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #b1b100;">foreach</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$key</span> <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$row</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
<span style="color: #b1b100;">echo</span> <span style="color: #990000;">key</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'=&gt;'</span><span style="color: #339933;">,</span> <span style="color: #990000;">current</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>会输出什么？</p>
<p>代码1会依次输出变量，但是第一个元素并没有在输出结果中出现。</p>
<p>代码2只会输出数组的第二个元素。</p>
<p>为什么呢？</p>
<p>将代码2在VLD扩展中查看，</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="shell" style="font-family:monospace;">number of ops:  22
compiled vars:  !0 = $arr, !1 = $key, !2 = $row
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   2     0  &gt;   INIT_ARRAY                                       ~0      1
         1      ADD_ARRAY_ELEMENT                                ~0      2
         2      ADD_ARRAY_ELEMENT                                ~0      3
         3      ADD_ARRAY_ELEMENT                                ~0      4
         4      ADD_ARRAY_ELEMENT                                ~0      5
         5      ASSIGN                                                   !0, ~0
   4     6    &gt; FE_RESET                                         $2      !0, -&gt;20
         7  &gt; &gt; FE_FETCH                                         $3      $2, -&gt;20
         8  &gt;   ZEND_OP_DATA                                     ~5      
         9      ASSIGN                                                   !2, $3
        10      ASSIGN                                                   !1, ~5
   5    11      SEND_REF                                                 !0
        12      DO_FCALL                                      1  $7      'key'
        13      ECHO                                                     $7
        14      ECHO                                                     '%3D%3E'
        15      SEND_REF                                                 !0
        16      DO_FCALL                                      1  $8      'current'
        17      ECHO                                                     $8
        18      ECHO                                                     '%0D%0A'
   6    19    &gt; JMP                                                      -&gt;7
        20  &gt;   SWITCH_FREE                                              $2
   8    21    &gt; RETURN                                                   1</pre></td></tr></table></div>

<p>从上面VLD扩展输出结果结合PHP的源代码可以知道，在foreach遍历之前, PHP内核首先会有个FE_RESET操作来重置数组的内部指针，也就是pInternalPointer, 然后通过每次FE_FETCH将pInternalPointer指向数组的下一个元素，从而实现顺序遍历。<br />
并且每次FE_FETCH的结果都会被一个全局的中间变量存储，以给下一次的获取元素使用。</p>
<p>从这两个例子可以引申出三个问题：</p>
<p><strong>1、为什么foreach循环体中执行key或current会显示第二个元素（非引用情况）？</strong><br />
以key函数为例，我们执行函数调用时，会执行中间代码SEND_REF，此中间代码会将没有设置引用的变量复制一份并设置为引用。当进入循环体时，PHP内核已经经过了一次fetch操作，相当于执行了一次next操作，当前元素指向第二个元素。因此我们在foreach的循环体中执行key函数时，key中调用的数组变量为PHP执行了一次fetch操作的数组拷贝，此时foreach的内部指针指向第二个元素。</p>
<p><strong>2、为什么在foreach中执行end等操作，其循环过程不变？</strong><br />
在遍历的代码中通过end，next等操作数组的指针，数组的指针不会变化，这是因为在PHP内核进行FETCH操作时，会通过中间变量存储当前操作数组的内部指针，每遍历一个元素，会先获取之前存储的指针位置，获取下一个元素后，再恢复指针位置。</p>
<p><strong>3、为什么$row的引用和非引用情况下输出结果不同？</strong><br />
如果是引用，PHP内核在reset数组时，会直接分裂数组，生成一个数组的拷贝，并将其设置为引用。<br />
如果是非引用，PHP内核在reset数组时，当数组的引用计数大于1，并且不存在引用时，会拷贝数组供foreach使用，其它情况使用原数组，将其引用计数加1。</p>
<p>因为引用的不同，在循环体中给函数传递参数时其结果不同，导致看到的foreach数组内部指针变化的不同。对于非引用且引用计数大于1的情况，其本身就是两个不同的数组，在RESET时就不同了。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2013/04/foreach-exception/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP的ticks机制</title>
		<link>https://www.phppan.com/2013/02/php-ticks/</link>
		<comments>https://www.phppan.com/2013/02/php-ticks/#comments</comments>
		<pubDate>Thu, 07 Feb 2013 09:18:38 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHP内核]]></category>
		<category><![CDATA[PHP源码]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>
		<category><![CDATA[深入理解PHP内核]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1790</guid>
		<description><![CDATA[PHP的ticks机制 要过年了，在年前完成这篇文章，如果有缘可以看到，祝福看到的朋友新年快乐，在新的一年里， [&#8230;]]]></description>
				<content:encoded><![CDATA[<h1>PHP的ticks机制</h1>
<p>要过年了，在年前完成这篇文章，如果有缘可以看到，祝福看到的朋友新年快乐，在新的一年里，万事顺意！</p>
<p>按今年的计划每个月至少有两篇文章，而一月份因为各种理由而只有一篇2012的总结，无论什么原因，总归是不对的。这篇算是补上的，也作为今年的开始。</p>
<p>回正题，今天要研究的是PHP的ticks机制。</p>
<p>PHP提供declare关键字和ticks关键字来声明ticks机制。如：declare(ticks = N);  这表示：在当前scope内，每执行N句internal  statements（opcodes），就会中断当前的业务语句，去执行通过register_tick_function注册的函数（如果存在的话），然后再继续之前的代码。需要注意的是这里的N是指的PHP的一些OPCODE，而OPCODE与我们见到的PHP语句却不是一一对应的。</p>
<p>最开始我以为PHP内核是在编译时记录是否有ticks机制，在真正执行中间代码时插入判断代码，实现此机制。但是事实上却不是这样滴。</p>
<p>看PHP代码示例1：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;">    <span style="color: #000088;">$name</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;phppan&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #000088;">$name</span><span style="color: #339933;">;</span>
    <span style="color: #000000; font-weight: bold;">class</span> Tipi <span style="color: #009900;">&#123;</span>
        <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> test<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
            <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">&quot;test&quot;</span><span style="color: #339933;">;</span>
        <span style="color: #009900;">&#125;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #000000; font-weight: bold;">function</span> f_tipi<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>如上代码包括了我们常见的几种语句，赋值，输出，定义类，定义函数。通常我们用VLD查看PHP生成的中间代码，上面的代码通过 <strong>php  -dvld.active=1 t.php</strong> 我们会看到 ECHO、ASSIGN、NOP等中间代码。</p>
<p>现在我们在示例1的代码上添加上ticks机制。如PHP代码示例2：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;">    <span style="color: #000000; font-weight: bold;">declare</span><span style="color: #009900;">&#40;</span>ticks<span style="color: #339933;">=</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$name</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;phppan&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #000088;">$name</span><span style="color: #339933;">;</span>
    <span style="color: #000000; font-weight: bold;">class</span> Tipi <span style="color: #009900;">&#123;</span>
        <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> test<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
            <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">&quot;test&quot;</span><span style="color: #339933;">;</span>
        <span style="color: #009900;">&#125;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #000000; font-weight: bold;">function</span> f_tipi<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>示例2与示例1相比也就是多了第一条语句： declare(ticks=1);  如果我们此时再次通过VLD查看中间代码，会发现在每个中间代码的后面都多了一句中间代码：<strong>TICKS</strong> 。</p>
<p>是否因为ticks=1的原因而在每个中间代码的后面添加了TICKS？将declare(ticks=1);换成declare(ticks=100);，再次VLD，结果没有变化。从以上的结果可以看出，PHP内核在语法分析过程中实现了ticks机制。</p>
<p>从实现过程来说定义ticks机制分为两个过程：一个是定义是否需要执行ticks或者说声明ticks机制，另一个实现在声明了ticks机制的情况下控制语句的执行。</p>
<h3>声明ticks机制过程</h3>
<p>声明的过程就是调用declare(ticks = N);  在语法分析时根据declare关键字和参数中的ticks关键字来声明ticks机制。通过zend_compile.c文件中的zend_do_declare_begin、declare_statement、zend_do_declare_end三个函数来编译声明ticks机制。在declare_statement函数中我们可以看到：declare除了可以声明ticks之外，还可以声明encoding，这在代码里面就是一个if  else的判断。</p>
<p>ticks机制的声明仅在编译过程有用，它为后面的声明控制语句服务。其编译过程中的全局变量为：CG(declarables)。这是一个结构体，它仅有一个成员：ticks。当然后面应该还会有更多的成员出现。</p>
<h3>声明控制语句</h3>
<p>示例1和示例2已经充分说明在每条语句的语法分析时，会根据是否声明了ticks机制来添加TICKS中间代码，其实现在于每条语句在语法解析时都会添加一条函数调用：zend_do_ticks。从zend_language_parser.y文件中可以看出：zend_do_ticks函数添加在类定义语句，函数定义语句和常规语句的后面。  zend_compile.c文件中的zend_do_ticks函数会根据前面提到的 CG(declarables).ticks 来判断是否生成  ZEND_TICKS 中间代码（在VLD中看到的中间代码都是没有ZEND开头）。</p>
<p>除了声明ticks机制，还有执行。执行过程中关键的变量是在声明时的ticks=N。其实这里的N可以换个角度去理解：ticks指定的数字是指执行了多少次TICKS语句。在TICKS中间代码的执行函数ZEND_TICKS_SPEC_CONST_HANDLER中，会统计执行当前函数的次数，存储变量为EG(ticks_count)。当达到当初声明的界限，就会调用一次所有通过register_tick_function注册的函数，并计数清零。</p>
<p>与当初自己设想的实现相比，PHP内核对ticks机制的实现满足了功能单一原则和松耦合原则。将ticks机制作为一个中间代码添加到整个中间代码的执行体系中，包括状态的转移，函数的切换这些都是直接使用原有的机制。</p>
<h2>ticks机制的应用场景</h2>
<p>手册上说：Ticks 很适合用来做调试，以及实现简单的多任务，后台 I/O 和很多其它任务。</p>
<p>在调试过程中，对于定位一段特定代码中速度慢的语句比较有用，我们可以每执行两条低级语句就记录一次时间。虽然这个过程也可以用其它方法完成，但用 tick  更方便也更容易实现。</p>
<p>PCNTL也使用ticks机制来作为信号处理机制（signal handle callback  mechanism），可以最小程度地降低处理异步事件时的负载。这里的关键在于PCNTL扩展的模块初始化函数（PHP_MINIT_FUNCTION(pcntl)）。在此模块做模块初始化时，它会调用：  php_add_tick_function(pcntl_signal_dispatch);将pcntl的分发执行函数添加到ticks机制的调用函数中去，从而当ticks触发时就会调用PCNTL扩展函数中指定的所有方法。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2013/02/php-ticks/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP的相似度计算函数：levenshtein</title>
		<link>https://www.phppan.com/2012/12/php-levenshtein-function/</link>
		<comments>https://www.phppan.com/2012/12/php-levenshtein-function/#comments</comments>
		<pubDate>Mon, 24 Dec 2012 01:02:48 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHP源码]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>
		<category><![CDATA[相似度]]></category>
		<category><![CDATA[编辑距离]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1774</guid>
		<description><![CDATA[在之前的文章 &#60;&#60; PHP中计算字符串相似度的函数 &#62;&#62;中我们介绍了similar_t [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>在之前的文章 <a href="http://www.phppan.com/2012/02/php-similar-text/" target="_blank">&lt;&lt;  PHP中计算字符串相似度的函数 &gt;&gt;</a>中我们介绍了similar_text函数的使用及实现过程。similar_text()  函数主要是用来计算两个字符串的匹配字符的数目，也可以计算两个字符串的相似度（以百分比计）。与 similar_text() 函数相比，我们今天要介绍的  levenshtein() 函数更快。不过，similar_text()  函数能通过更少的必需修改次数提供更精确的结果。在追求速度而少精确度，并且字符串长度有限时可以考虑使用 levenshtein() 函数。</p>
<h2>使用说明</h2>
<p>先看手册上 levenshtein() 函数的说明：</p>
<p>levenshtein() 函数返回两个字符串之间的 Levenshtein 距离。</p>
<p>Levenshtein  距离，又称编辑距离，指的是两个字符串之间，由一个转换成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符，插入一个字符，删除一个字符。</p>
<p>例如把 kitten 转换为 sitting：</p>
<pre>sitten （k→s）
sittin （e→i）
sitting （→g）</pre>
<p>levenshtein() 函数给每个操作（替换、插入和删除）相同的权重。不过，您可以通过设置可选的 insert、replace、delete  参数，来定义每个操作的代价。</p>
<p>语法：</p>
<p>levenshtein(string1,string2,insert,replace,delete)</p>
<p>参数 描述</p>
<ul>
<li>string1 必需。要对比的第一个字符串。</li>
<li>string2 必需。要对比的第二个字符串。</li>
<li>insert 可选。插入一个字符的代价。默认是 1。</li>
<li>replace 可选。替换一个字符的代价。默认是 1。</li>
<li>delete 可选。删除一个字符的代价。默认是 1。</li>
</ul>
<p>提示和注释</p>
<ul>
<li>如果其中一个字符串超过 255 个字符，levenshtein() 函数返回 -1。</li>
<li>levenshtein() 函数对大小写不敏感。</li>
<li>levenshtein() 函数比 similar_text() 函数更快。不过，similar_text() 函数提供需要更少修改的更精确的结果。</li>
</ul>
<p>例子</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #990000;">levenshtein</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;Hello World&quot;</span><span style="color: #339933;">,</span><span style="color: #0000ff;">&quot;ello World&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">&quot;&lt;br /&gt;&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #990000;">levenshtein</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;Hello World&quot;</span><span style="color: #339933;">,</span><span style="color: #0000ff;">&quot;ello World&quot;</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">10</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">20</span><span style="color: #339933;">,</span><span style="color: #cc66cc;">30</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000000; font-weight: bold;">?&gt;</span></pre></td></tr></table></div>

<p>输出： 1 30</p>
<h2>源码分析</h2>
<p>levenshtein() 属于标准函数，在/ext/standard/目录下有专门针对此函数实现的文件：levenshtein.c。</p>
<p>levenshtein()会根据参数个数选择实现方式，针对参数为2和参数为5的情况，都会调用 reference_levdist()  函数计算距离。其不同在于对后三个参数，参数为2时，使用默认值1。</p>
<p>并且在实现源码中我们发现了一个在文档中没有说明的情况： levenshtein() 函数还可以传递三个参数，其最终会调用 custom_levdist()  函数。它将第三个参数作为自定义函数的实现，其调用示例如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"> <span style="color: #b1b100;">echo</span> <span style="color: #990000;">levenshtein</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;Hello World&quot;</span><span style="color: #339933;">,</span><span style="color: #0000ff;">&quot;ello World&quot;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'strsub'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>执行会报Warning： The general Levenshtein support is not there  yet。这是因为现在这个方法还没有实现，仅仅是放了一个坑在那。</p>
<p>reference_levdist() 函数的实现算法是一个经典的DP问题。</p>
<p>给定两个字符串x和y，求最少的修改次数将x变成y。修改的规则只能是如下三种之一：删除、插入、改变。<br />
用a[i][j]表示把x的前i个字符变成y的前j个字符所需的最少操作次数，则状态转移方程为：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="shell" style="font-family:monospace;">当x[i]==y[j]时：a[i][j]  = min(a[i-1][j-1], a[i-1][j]+1, a[i][j-1]+1);
当x[i]!=y[j]时：a[i][j] =  min(a[i-1][j-1], a[i-1][j], a[i][j-1])+1;</pre></td></tr></table></div>

<p>在用状态转移方程前，我们需要初始化(n+1)<em>(m+1)的矩阵d，并让第一行和列的值从0开始增长。  扫描两字符串（n</em>m级的），对比字符，使用状态转移方程，最终$a[$l1][$l2]为其结果。</p>
<p>简单实现过程如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?PHP</span>
    <span style="color: #000088;">$s1</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;abcdd&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$l1</span> <span style="color: #339933;">=</span> <span style="color: #990000;">strlen</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$s1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$s2</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;aabbd&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$l2</span> <span style="color: #339933;">=</span> <span style="color: #990000;">strlen</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$s2</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
&nbsp;
    <span style="color: #b1b100;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">&lt;</span> <span style="color: #000088;">$l1</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span><span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #b1b100;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">&lt;</span> <span style="color: #000088;">$l2</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span><span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #b1b100;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">&lt;</span> <span style="color: #000088;">$l2</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span><span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #b1b100;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$j</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> <span style="color: #000088;">$j</span> <span style="color: #339933;">&lt;</span> <span style="color: #000088;">$l1</span><span style="color: #339933;">;</span> <span style="color: #000088;">$j</span><span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
            <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$s2</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">==</span> <span style="color: #000088;">$s1</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
                <span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #990000;">min</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span> <span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span><span style="color: #b1b100;">else</span><span style="color: #009900;">&#123;</span>
                <span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #990000;">min</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span>
        <span style="color: #009900;">&#125;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #b1b100;">echo</span> <span style="color: #000088;">$a</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$l1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$l2</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #990000;">levenshtein</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$s1</span><span style="color: #339933;">,</span> <span style="color: #000088;">$s2</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>在PHP的实现中，实现者在注释中很清楚的标明：此函数仅优化了内存使用，而没有考虑速度，从其实现算法看，时间复杂度为O(m×n)。其优化点在于将上面的状态转移方程中的二维数组变成了两个一组数组。简单实现如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?PHP</span>
    <span style="color: #000088;">$s1</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;abcjfdkslfdd&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$l1</span> <span style="color: #339933;">=</span> <span style="color: #990000;">strlen</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$s1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$s2</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;aab84093840932bd&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #000088;">$l2</span> <span style="color: #339933;">=</span> <span style="color: #990000;">strlen</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$s2</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #000088;">$dis</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">&lt;=</span> <span style="color: #000088;">$l2</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span><span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$p1</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$i</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #b1b100;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$i</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">&lt;</span> <span style="color: #000088;">$l1</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span><span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$p2</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$p1</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">0</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #b1b100;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$j</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span><span style="color: #339933;">;</span> <span style="color: #000088;">$j</span> <span style="color: #339933;">&lt;</span> <span style="color: #000088;">$l2</span><span style="color: #339933;">;</span> <span style="color: #000088;">$j</span><span style="color: #339933;">++</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
            <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #000088;">$s1</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$i</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">==</span> <span style="color: #000088;">$s2</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#123;</span>
                <span style="color: #000088;">$dis</span> <span style="color: #339933;">=</span> <span style="color: #990000;">min</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$p1</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$p1</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span> <span style="color: #000088;">$p2</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span><span style="color: #b1b100;">else</span><span style="color: #009900;">&#123;</span>
                <span style="color: #000088;">$dis</span> <span style="color: #339933;">=</span> <span style="color: #990000;">min</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$p1</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span> <span style="color: #000088;">$p1</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #339933;">,</span> <span style="color: #000088;">$p2</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>  <span style="color: #666666; font-style: italic;">// 注意这里最后一个参数为$p2  </span>
            <span style="color: #009900;">&#125;</span>
            <span style="color: #000088;">$p2</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$j</span> <span style="color: #339933;">+</span> <span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$dis</span><span style="color: #339933;">;</span>
        <span style="color: #009900;">&#125;</span>
        <span style="color: #000088;">$tmp</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$p1</span><span style="color: #339933;">;</span>
        <span style="color: #000088;">$p1</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$p2</span><span style="color: #339933;">;</span>
        <span style="color: #000088;">$p2</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$tmp</span><span style="color: #339933;">;</span>  
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #000088;">$p1</span><span style="color: #009900;">&#91;</span><span style="color: #000088;">$l2</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #990000;">levenshtein</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$s1</span><span style="color: #339933;">,</span> <span style="color: #000088;">$s2</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>如上为PHP内核开发者对前面经典DP的优化，其优化点在于不停的复用两个一维数组，一个记录上次的结果，一个记录这一次的结果。如果按照PHP的参数，分别给三个操作赋值不同的值，在上面的算法中将对应的1变成操作对应的值就可以了。  min函数的第一个参数对应的是修改，第二个参数对应的是删除，第三个参数对应的是添加。</p>
<h2>Levenshtein distance说明</h2>
<p><a href="http://en.wikipedia.org/wiki/Levenshtein_distance" target="_blank">Levenshtein  distance</a>最先是由俄国科学家Vladimir Levenshtein在1965年发明，用他的名字命名。不会拼读，可以叫它edit  distance（编辑距离）。Levenshtein distance可以用来：</p>
<ul>
<li>Spell checking(拼写检查)</li>
<li>Speech recognition(语句识别)</li>
<li>DNA analysis(DNA分析)</li>
<li>Plagiarism detection(抄袭检测) <em> LD用m</em>n的矩阵存储距离值。</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2012/12/php-levenshtein-function/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP内核中的异常基类</title>
		<link>https://www.phppan.com/2012/10/php-exception-class/</link>
		<comments>https://www.phppan.com/2012/10/php-exception-class/#comments</comments>
		<pubDate>Sun, 28 Oct 2012 23:03:52 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHP源码]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>
		<category><![CDATA[TIPI]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1748</guid>
		<description><![CDATA[PHP内核中的异常基类 在&#60;&#60;思考PHP语言三：异常处理&#62;&#62;中有说到异常的定义：异常处 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2>PHP内核中的异常基类</h2>
<p>在<a href="http://www.phppan.com/2010/12/thinkinphp-3-exception/">&lt;&lt;思考PHP语言三：异常处理&gt;&gt;</a>中有说到异常的定义：异常处理是指在语言中能够使程序按照一种标准的方法对于某些运行时错误和其他程序所检测到的异常事件做出反应。异常发生的时间是不可以确定的，如果一种语言不包括异常处理机制，这就会给语言带来额外的复杂性。对于异常的处理有三种方案，而PHP5使用的是将一个异常处理独立出来，作为专门的子程序或类存在。</p>
<p><a href="http://cn2.php.net/manual/zh/class.exception.php">Exception</a>是PHP中所有异常的基类，自从PHP5.1.0开始引入，自此，我们可以以面向对象的方式处理错误。  Exception类的声明如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;">&nbsp;
 Exception <span style="color: #009900;">&#123;</span>
        <span style="color: #666666; font-style: italic;">/* 属性 */</span>
        <span style="color: #000000; font-weight: bold;">protected</span> string <span style="color: #000088;">$message</span> <span style="color: #339933;">;</span>
        <span style="color: #000000; font-weight: bold;">protected</span> int <span style="color: #000088;">$code</span> <span style="color: #339933;">;</span>
        <span style="color: #000000; font-weight: bold;">protected</span> string <span style="color: #000088;">$file</span> <span style="color: #339933;">;</span>
        <span style="color: #000000; font-weight: bold;">protected</span> int <span style="color: #000088;">$line</span> <span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #666666; font-style: italic;">/* 方法 */</span>
        <span style="color: #000000; font-weight: bold;">public</span> __construct <span style="color: #009900;">&#40;</span><span style="color: #009900;">&#91;</span> string <span style="color: #000088;">$message</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;&quot;</span> <span style="color: #009900;">&#91;</span><span style="color: #339933;">,</span> int <span style="color: #000088;">$code</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">0</span> <span style="color: #009900;">&#91;</span><span style="color: #339933;">,</span> Exception <span style="color: #000088;">$previous</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">NULL</span> <span style="color: #009900;">&#93;</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#93;</span> <span style="color: #009900;">&#41;</span>
        final <span style="color: #000000; font-weight: bold;">public</span> string getMessage <span style="color: #009900;">&#40;</span> void <span style="color: #009900;">&#41;</span>
        final <span style="color: #000000; font-weight: bold;">public</span> Exception getPrevious <span style="color: #009900;">&#40;</span> void <span style="color: #009900;">&#41;</span>
        final <span style="color: #000000; font-weight: bold;">public</span> int getCode <span style="color: #009900;">&#40;</span> void <span style="color: #009900;">&#41;</span>
        final <span style="color: #000000; font-weight: bold;">public</span> string getFile <span style="color: #009900;">&#40;</span> void <span style="color: #009900;">&#41;</span>
        final <span style="color: #000000; font-weight: bold;">public</span> int getLine <span style="color: #009900;">&#40;</span> void <span style="color: #009900;">&#41;</span>
        final <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #990000;">array</span> getTrace <span style="color: #009900;">&#40;</span> void <span style="color: #009900;">&#41;</span>
        final <span style="color: #000000; font-weight: bold;">public</span> string getTraceAsString <span style="color: #009900;">&#40;</span> void <span style="color: #009900;">&#41;</span>
        <span style="color: #000000; font-weight: bold;">public</span> string __toString <span style="color: #009900;">&#40;</span> void <span style="color: #009900;">&#41;</span>
        final <span style="color: #000000; font-weight: bold;">private</span> void __clone <span style="color: #009900;">&#40;</span> void <span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>其中message表示异常消息内容，code表示异常代码，file表示抛出异常的文件名，line表示抛出异常在该文件中的行号。下面从  PHP内核的角度说明这些属性及对应的方法。</p>
<p>message表示异常的消息内容，其对应getMessage方法。message是自定义的异常消息，默认为空字符串。对于PHP内核来说，创建Exception对象时，有无message参数会影响  getMessage方法的返回值，以及显示异常时是否有with message  %s等字样。message成员变量的作用是为了让用户更好的定义说明异常类。</p>
<p>code表示异常代码，其对应getCode方法。和meesage成员变量一样，code也是用户自定义的内容，默认为0。</p>
<p>file表示抛出异常的文件名，其对应getFile方法，返回值为执行文件的文件名，在PHP内核中存储此文件名的字段为  EG(active_op_array)-&gt;filename  此字段的值在生成一个opcode列表时，PHP的内核会将此前正在编译文件的文件名赋值给opcode的filename属性，如生成一个函数的op_array，在初始化op_array时，会执行上面所说的赋值操作，这里的赋值是通过编译的全局变量来传递的。当代码执行时，EG(active_op_array)表示正在执行的opcode列表。</p>
<p>line表示抛出异常在该文件中的行号，其对应getLine方法，返回整数，即EG(opline_ptr)-&gt;lineno。对于每条PHP脚本生成的opcode，在编译时都会执行一次初始化操作，在这次初始化操作中，PHP内核会将当前正在编译的行号赋值给opcode的lineno属性。  EG(opline_ptr)是PHP内核执行的当前opcode，抛出异常时对应的行号即为此对象的lineno属性。</p>
<p>除了上面四个属性，异常类还包括一个非常重要的内容：异常的追踪信息。在异常类中，通过getTrace方法可以获取这些信息。此方法的作用相当于PHP的内置函数debug_backtrace。在代码实现层面他们最终都是调用zend_fetch_debug_backtrace函数。在此函数中通过回溯PHP的调用栈，返回代码追踪信息。与getTrace方法对应还有一个返回被串化值的方法getTraceAsString，以字符串替代数组返回异常追踪信息。</p>
<p>在构造函数中，从PHP5.3.0增加$previous参数，表示异常链中的前一个异常。在catch块中可以抛出一个新的异常，并引用原始的异常，为调试提供更多的信息。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2012/10/php-exception-class/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>几个 PHP 的“魔术常量”</title>
		<link>https://www.phppan.com/2012/05/php-magic-const-var/</link>
		<comments>https://www.phppan.com/2012/05/php-magic-const-var/#comments</comments>
		<pubDate>Mon, 28 May 2012 00:43:44 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHP源码]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>
		<category><![CDATA[常量]]></category>
		<category><![CDATA[魔术常量]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1689</guid>
		<description><![CDATA[PHP向它运行的任何脚本提供了大量的预定义常量。 不过很多常量都是由不同的扩展库定义的，只有在加载了这些扩展库 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>PHP向它运行的任何脚本提供了大量的预定义常量。<br />
不过很多常量都是由不同的扩展库定义的，只有在加载了这些扩展库时才会出现，或者动态加载后，或者在编译时已经包括进去了。</p>
<p>有七个魔术常量它们的值随着它们在代码中的位置改变而改变。例如 __LINE__ 的值就依赖于它在脚本中所处的行来决定。这些特殊的常量不区分大小写。在<a href="http://cn2.php.net/manual/zh/language.constants.predefined.php">手册</a>中这几个变量的简单说明如下：<br />
几个 PHP 的“魔术常量”</p>
<table>
<tr>
<th>名称</th>
<th>说明</th>
</tr>
<tr>
<td>__LINE__</td>
<td>文件中的当前行号。</td>
</tr>
<tr>
<td>__FILE__</td>
<td>文件的完整路径和文件名。如果用在被包含文件中，则返回被包含的文件名。自 PHP 4.0.2 起，__FILE__ 总是包含一个绝对路径（如果是符号连接，则是解析后的绝对路径），而在此之前的版本有时会包含一个相对路径。</td>
</tr>
<tr>
<td>__DIR__</td>
<td>文件所在的目录。如果用在被包括文件中，则返回被包括的文件所在的目录。它等价于 dirname(__FILE__)。除非是根目录，否则目录中名不包括末尾的斜杠。（PHP 5.3.0中新增） =</td>
</tr>
<tr>
<td>__FUNCTION__</td>
<td>函数名称（PHP 4.3.0 新加）。自 PHP 5 起本常量返回该函数被定义时的名字（区分大小写）。在 PHP 4 中该值总是小写字母的。</td>
</tr>
<tr>
<td>__CLASS__</td>
<td>类的名称（PHP 4.3.0 新加）。自 PHP 5 起本常量返回该类被定义时的名字（区分大小写）。在 PHP 4 中该值总是小写字母的。</td>
</tr>
<tr>
<td>__METHOD__</td>
<td>类的方法名（PHP 5.0.0 新加）。返回该方法被定义时的名字（区分大小写）。</td>
</tr>
<tr>
<td>__NAMESPACE__</td>
<td>当前命名空间的名称（大小写敏感）。这个常量是在编译时定义的（PHP 5.3.0 新增）</td>
</tr>
<tr>
</table>
<p>在写这篇文章时，由于把思维局限在语法解析和运行时赋值，导致寻找了好久都没有发现实现原因。还是<a href="http://www.csie.nctu.edu.tw/~chlo/web/docs/doc/data/php/12.htm">网上的一篇文章</a>将我们的思维找回到词法分析。PHP内核会在词法解析时将这些相对静态的内容赋值给这些变量，而不是在运行时执行的分析。如下PHP代码：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?PHP</span>
<span style="color: #b1b100;">echo</span> <span style="color: #009900; font-weight: bold;">__LINE__</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">function</span> demo<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
	<span style="color: #b1b100;">echo</span> <span style="color: #009900; font-weight: bold;">__FUNCTION__</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
demo<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>其实PHP已经在词法解析时将这些常量换成了对应的值，以上的代码可以看成如下的PHP代码：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?PHP</span>
<span style="color: #b1b100;">echo</span> <span style="color: #cc66cc;">2</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">function</span> demo<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
	<span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">&quot;demo&quot;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
demo<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>如果我们使用VLD扩展查看以上的两段代码生成的中间代码，你会发现其结果是一样的。</p>
<p>前面我们有说PHP是在词法分析时做的赋值替换操作，以__FUNCTION__为例，在Zend/zend_language_scanner.l文件中，__FUNCTION__是一个需要分析的元标记（token）：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;"><span style="color: #339933;">&lt;</span>ST_IN_SCRIPTING<span style="color: #339933;">&gt;</span><span style="color: #ff0000;">&quot;__FUNCTION__&quot;</span> <span style="color: #009900;">&#123;</span>
	<span style="color: #993333;">char</span> <span style="color: #339933;">*</span>func_name <span style="color: #339933;">=</span> NULL<span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span>CG<span style="color: #009900;">&#40;</span>active_op_array<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
		func_name <span style="color: #339933;">=</span> CG<span style="color: #009900;">&#40;</span>active_op_array<span style="color: #009900;">&#41;</span><span style="color: #339933;">-&gt;</span>function_name<span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
&nbsp;
	<span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span>func_name<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
		func_name <span style="color: #339933;">=</span> <span style="color: #ff0000;">&quot;&quot;</span><span style="color: #339933;">;</span>
	<span style="color: #009900;">&#125;</span>
	zendlval<span style="color: #339933;">-&gt;</span>value.<span style="color: #202020;">str</span>.<span style="color: #202020;">len</span> <span style="color: #339933;">=</span> <span style="color: #000066;">strlen</span><span style="color: #009900;">&#40;</span>func_name<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	zendlval<span style="color: #339933;">-&gt;</span>value.<span style="color: #202020;">str</span>.<span style="color: #202020;">val</span> <span style="color: #339933;">=</span> estrndup<span style="color: #009900;">&#40;</span>func_name<span style="color: #339933;">,</span> zendlval<span style="color: #339933;">-&gt;</span>value.<span style="color: #202020;">str</span>.<span style="color: #202020;">len</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	zendlval<span style="color: #339933;">-&gt;</span>type <span style="color: #339933;">=</span> IS_STRING<span style="color: #339933;">;</span>
	<span style="color: #b1b100;">return</span> T_FUNC_C<span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>就是这里，当当前中间代码处于一个函数中时，则将当前函数名赋值给zendlval，如果没有，则将空字符串赋值给zendlval(因此在顶级作用域名中直接打印__FUNCTION__会输出空格)。这个值在语法解析时会直接赋值给返回值。这样我们就在生成的中间代码中看到了这些常量的位置都已经赋值好了。</p>
<p>和__FUNCTION__类似，在其附近的位置，上面表格中的其它常量也进行了类似的操作。在PHP5.4中增加了对于trait类的常量定义：__TRAIT__。</p>
<p><strong>这些常量其实相当于一个常量模板，或者说是一个占位符，在词法解析时这些模板或占位符就被替换成实际的值 </strong></p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2012/05/php-magic-const-var/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>从PHP的自动测试想到的</title>
		<link>https://www.phppan.com/2012/04/php-test-and-monitor/</link>
		<comments>https://www.phppan.com/2012/04/php-test-and-monitor/#comments</comments>
		<pubDate>Mon, 23 Apr 2012 00:59:52 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[程序相关]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>
		<category><![CDATA[监控框架]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1663</guid>
		<description><![CDATA[从PHP的自动测试想到的 昨日，因TIPI项目而阅读了PHP的自动测试实现相关代码。于此，有些许感想，记录如下 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>从PHP的自动测试想到的</p>
<p>昨日，因TIPI项目而阅读了PHP的自动测试实现相关代码。于此，有些许感想，记录如下。</p>
<p>1、用自己测试自己，制定测试过程规范。<br />
PHP的测试环境是用PHP实现的，这不得不说是一个创新之举。相对于编译型语言，作为动态语言的PHP在应对变化上有着不少的优势，而测试本来就是一个变化是非较多的地儿。其实用到了PHP的地方只是这个框架的控制器，即源码根目录下run-tests.php文件。作为控制器，它实现了整个测试过程的控制。以一个测试过程为例，总体上分为三个部分：准备、运行和显示结果。准备活动包括测试所必须的环境变量的读取与设置，对测试参数的解析，测试脚本名的解析，各种输出文件的准备 解析测试脚本中的各个段落等；运行活动包括构造测试语句，执行测试语句，得到实际运行结果；显示结果活动包括测试后的结果比对及输出，相关记录记录以及总的测试报告显示。</p>
<p>这个控制器就是PHP自动测试的规范，所有的逻辑都在这一个脚本文件中，在一个时间点上，这是一个不变的过程。对于测试中变化的内容如测试环境，测试输入数据、需要验证的内容以及针对不同输入和不同测试点应该得到的预期结果，这些都存储在PHPT文件中，以不同的标记作为段分开。这些文件按模块划分，一个用例就是一个文件，与将用例写成代码相比，优势不仅仅在于工作量，更多的是在于它的扩展性、可读性和可维护性。</p>
<p>2、简单监控框架</p>
<p>先确认我们这个监控框架的需求什么。现在我们要的是一个可以监控数据是否正常，数据的状态是否符合业务逻辑，并将监控的结果发给相关负责人。从这个简单的需求出发，我们可以发现这里变化的是监控的内容，而不变的是整个监控的流程：查询特定的数据源，根据具体业务确认数据的正确性和合理性，并将结果发送给相关责任人。</p>
<p>对于不变的因素，我们可以以公共模块的方式在代码中实现，如果汇报结果的形式有不同的分类和权限控制的话，我们可以将这些配置放到数据库，当然，我们还是需要在代码中实现这些汇报的方式。</p>
<p>对于变化的因素，我们可以学习PHP的测试过程，以某些特定的规则定义一个一个的监控，我们可以称之为监控用例。在用例中定义名称、输入、过程和预期结果。比如，我们可以定义&#8211;SQL&#8211;字段做数据源。当然，这些内容我们可以分散存储，也可以集中存储在数据库。</p>
<p>这样一种以测试的方式实现监控过程，也许可以试试。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2012/04/php-test-and-monitor/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP文件上传进度的实现原理</title>
		<link>https://www.phppan.com/2012/04/php-upload-progress/</link>
		<comments>https://www.phppan.com/2012/04/php-upload-progress/#comments</comments>
		<pubDate>Fri, 20 Apr 2012 00:51:38 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHP内核]]></category>
		<category><![CDATA[PHP源码]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1648</guid>
		<description><![CDATA[在PHP5.4之前，如果我们要获取文件上传的进度，可以选择的方案有Flash或使用PHP的uploadprog [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>在PHP5.4之前，如果我们要获取文件上传的进度，可以选择的方案有Flash或使用PHP的<a href="http://pecl.php.net/package/uploadprogress" target="_blank">uploadprogress扩展</a>。这两种方案存在本质的区别，Flash的上传进度是客户端上传的进度，它是基于本地OS的网络传输，最终其本质上也是一次HTTP的multipart/form-data编码的POST请求；uploadprogress扩展需要依靠JS获取服务器提供的进度，这里的进度是服务器接收的文件进度。</p>
<p>而在PHP5.4之后，我们可以在不添加扩展的情况下，从session数据中获取了文件上传的进度。uploadprogress扩展和PHP5.4的session扩展都能获取上传的进度，其是否有相同的地方呢？</p>
<p>我们先来看uploadprogress扩展，下载源码包，解圧，直接打开文件，我们可以在example中找到一个简单的示例。在info.php文件中，uploadprogress_get_info函数用来获取上传文件进度。upploadprogress.c文件存储了扩展的实现过程。uploadprogress扩展实现的关键在于其模块寝化函数：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;">PHP_MINIT_FUNCTION<span style="color: #009900;">&#40;</span>uploadprogress<span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
	REGISTER_INI_ENTRIES<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	php_rfc1867_callback <span style="color: #339933;">=</span> uploadprogress_php_rfc1867_file<span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #b1b100;">return</span> SUCCESS<span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>此函数的核心就是设置php_rfc1867_callback为uploadprogress_php_rfc1867_file。<br />
设置这个函数指针有什么用呢？<br />
在前面的文章<a href="http://www.phppan.com/2012/03/files-type/" target="_blank">PHP内核中文件上传类型的获取过程</a>中我们了解到PHP处理POST请求的函数是SAPI_POST_HANDLER_FUNC(rfc1867_post_handler)(main/rfc1867.c)。在这里， 我们发现了若干个php_rfc1867_callback的调用，从调用的第一个参数来看，它可以分为六个事件，或者说有六个回调更新点。</p>
<p>如果此时我们查看PHP5.4的的session扩展的实现文件session.c时，搜索php_rfc1867_callback，你会发现在模块初始化函数中也有与扩展类似的赋值操作：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;">	php_rfc1867_callback <span style="color: #339933;">=</span> php_session_rfc1867_callback<span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>同样，在php_session_rfc1867_callback函数中有与uploadprogress同样的六个事件的处理，这六个事件相当于六个钩子程序，分别对应POST请求的处理的六个不同的位置，在PHP5.4中他们的作用分别是：</p>
<ul>
<li>
1、MULTIPART_EVENT_START 在处理所有的请求实体之前，初始化上传进度信息，比用于记录上传进度相关信息的progress结构体信息（如content-length）
</li>
<li>
2、MULTIPART_EVENT_FORMDATA 对于每个multipart包含的控制，执行此步初始化操作，以此之前会解析Content-Disposition相关属性，并初始化progress的其它信息，如session_id,以及整个上传活动的key，这里表示整个上传进度准备好了。
</li>
<li>
3、MULTIPART_EVENT_FILE_START 开始处理上传的文件信息，如果progress的data不存在，则会创建此结构，并初始化session中存储的对于此次文件上传的start_time、content_length、bytes_processed、files等信息。然后处理单个文件的上传属性，如field_name、tmp_name等。对于tmp_name等字段这里是执行初始化操作。这一步的时候获取session 的值才会开始有上传进度的相关信息。
</li>
<li>
4、MULTIPART_EVENT_FILE_DATA 更新上传文件的长度，在一堆的文件相关信息检测和临时文件写入之前，也是在将数据写入到$_FILES之前。
</li>
<li>
5、MULTIPART_EVENT_FILE_END 单个文件上传结束，此时会更新这个文件相关的一些信息，比如error, tmp_name，tmp_name字段在start时是null。当然这里还有针对当前文件的done字段的更新。
</li>
<li>
6、MULTIPART_EVENT_END 更新session数组的最后的一些结信息 比如done字段 并清空progress的信息，
</li>
</ul>
<p>这里的六个事件是相同的，而uploadprogress扩展和PHP5.4的session扩展在事件处理过程中中间存储结构和最后的返回内容与方式上存在一些差异。uploadprogress扩展的存储结构为一个按照扩展制定的规则生成的临时文件，最后是通过扩展函数uploadprogress_get_info返回上传进度的数组。PHP5.4的存储结构为SESSION的存储方式，或者是文件，或者是memcache，这个按session的设置来，其最终是通过$_SESSION返回相关数组。</p>
<p>除了uploadprogress扩展外，APC也以设置php_rfc1867_callback = apc_rfc1867_progress，提供了类似的解决方案，启动此功能需要在php.ini中设置apc.rfc1867项为启用，并且在表单中加一个隐藏域 APC_UPLOAD_PROGRESS，这个域的值可以随机生成一个hash，以确定此次上传操作的唯一性。通过Ajax调用服务端显示进度的接口，在接口中通过apc_fetch函数获取APC缓存的文件上传进度。比如print_r(apc_fetch(&#8220;upload_$_POST[APC_UPLOAD_PROGRESS]&#8220;));可以得到如下结果：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #990000;">Array</span>
<span style="color: #009900;">&#40;</span>
    <span style="color: #009900;">&#91;</span>total<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=&gt;</span> <span style="color: #cc66cc;">1142543</span>
    <span style="color: #009900;">&#91;</span><span style="color: #990000;">current</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=&gt;</span> <span style="color: #cc66cc;">1142543</span>
    <span style="color: #009900;">&#91;</span>rate<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=&gt;</span> <span style="color:#800080;">1828068.8</span>
    <span style="color: #009900;">&#91;</span>filename<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=&gt;</span> test
    <span style="color: #009900;">&#91;</span>name<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=&gt;</span> <span style="color: #990000;">file</span>
    <span style="color: #009900;">&#91;</span>temp_filename<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=&gt;</span> <span style="color: #339933;">/</span>tmp<span style="color: #339933;">/</span>php8F
    <span style="color: #009900;">&#91;</span>cancel_upload<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=&gt;</span> <span style="color: #cc66cc;">0</span>
    <span style="color: #009900;">&#91;</span>done<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=&gt;</span> <span style="color: #cc66cc;">1</span>
<span style="color: #009900;">&#41;</span></pre></td></tr></table></div>

<p>apc.rfc1867相关更加详细的内容猛击 <a href="http://cn2.php.net/manual/en/apc.configuration.php#ini.apc.rfc1867" target="_blank">APC Runtime Configuration</a></p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2012/04/php-upload-progress/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP中的字符串连接操作</title>
		<link>https://www.phppan.com/2012/03/php-string-concat/</link>
		<comments>https://www.phppan.com/2012/03/php-string-concat/#comments</comments>
		<pubDate>Mon, 26 Mar 2012 01:12:12 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[PHP内核]]></category>
		<category><![CDATA[PHP源码]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>
		<category><![CDATA[字符串]]></category>
		<category><![CDATA[深入理解PHP内核]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1630</guid>
		<description><![CDATA[上周和刘志强同学讨论字符串的连接操作： 一般情况下我们用点号做字符串的连接操作，但是如果在某个长字符串中放一个 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>上周和<a href="http://liuzhiqiangruc.iteye.com/">刘志强同学</a>讨论字符串的连接操作：<br />
一般情况下我们用点号做字符串的连接操作，但是如果在某个长字符串中放一个变量，通常我们会采用在字符串中直接写入一个变量的方式来实现</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000088;">$var</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">10</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$str</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;test string begin &quot;</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$var</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot; end&quot;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">//或</span>
<span style="color: #000088;">$var</span> <span style="color: #339933;">=</span> <span style="color: #cc66cc;">10</span><span style="color: #339933;">;</span>
<span style="color: #000088;">$str</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">&quot;test string begin <span style="color: #006699; font-weight: bold;">$var</span> end&quot;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>这二者有什么区别呢？</p>
<p>以VLD扩展直接查看这两段代码生成的中间代码：<br />
点号连接：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="shell" style="font-family:monospace;">number of ops:  7
compiled vars:  !0 = $var, !1 = $str
line     # *  op         ext  return  operands
------------------------------------------------
   2     0  &gt;   EXT_STMT
         1      ASSIGN                  !0, 10
   3     2      EXT_STMT
         3      CONCAT          ~1      'test+string+begin+', !0
         4      CONCAT          ~2      ~1, '+end'
         5      ASSIGN                  !1, ~2
         6    &gt; RETURN                  1</pre></td></tr></table></div>

<p>直接在字符串中插入变量：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="shell" style="font-family:monospace;">number of ops:  8
compiled vars:  !0 = $var, !1 = $str
line     # *  op             ext  return  operands
----------------------------------------------------
   2     0  &gt;   EXT_STMT
         1      ASSIGN                      !0, 10
   3     2      EXT_STMT
         3      ADD_STRING          ~1      'test+string+begin+'
         4      ADD_VAR             ~1      ~1, !0
         5      ADD_STRING          ~1      ~1, '+end'
         6      ASSIGN                      !1, ~1
         7    &gt; RETURN                      1</pre></td></tr></table></div>

<p>对比这段生成的中间码，其原理完全不一样：</p>
<p>点号是典型的连接操作（当然，它本来就是连接操作），<br />
当使用多个点号是，每两个点号的结果都会使用一个临时变量存储起来，并作为下一个操作的一个操作数。如在我们的示例中，首先是执行第一个连接操作，将“test string begin ”和$var连接起来，得到“test string begin 10”，然后再执行第二个连接操作，将上一个操作得到的结果“test string begin 10”和&#8221; end&#8221;连接起来，并将结果存储在另一个临时变量，最后将第二个连接操作的结果赋值给$str。</p>
<p>连接操作对应的opcode为ZEND_CONCAT，对于所给的两个操作数，其最终通过concat_function函数将两个字符串连接起来，如果所给的变量的类型不是字符串，则会通过zend_make_printable_zval将其转换成字符串。concat_function函数会根据两个字符串的长度重新分配内存，并执行两次拷贝操作，将两个字符串拷贝到新的内存空间。<br />
这里针对两个字符串相同的情况有一个特殊处理。<br />
如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;"><span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span>result<span style="color: #339933;">==</span>op1<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>	<span style="color: #808080; font-style: italic;">/* special case, perform operations on result */</span>
	uint res_len <span style="color: #339933;">=</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op1<span style="color: #009900;">&#41;</span> <span style="color: #339933;">+</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op2<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
	Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #339933;">=</span> erealloc<span style="color: #009900;">&#40;</span>Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> res_len<span style="color: #339933;">+</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
	<span style="color: #000066;">memcpy</span><span style="color: #009900;">&#40;</span>Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">+</span>Z_STRLEN_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> Z_STRVAL_P<span style="color: #009900;">&#40;</span>op2<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op2<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span>res_len<span style="color: #009900;">&#93;</span><span style="color: #339933;">=</span><span style="color: #0000dd;">0</span><span style="color: #339933;">;</span>
	Z_STRLEN_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #339933;">=</span> res_len<span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span> <span style="color: #b1b100;">else</span> <span style="color: #009900;">&#123;</span>
	Z_STRLEN_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #339933;">=</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op1<span style="color: #009900;">&#41;</span> <span style="color: #339933;">+</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op2<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #339933;">=</span> <span style="color: #009900;">&#40;</span><span style="color: #993333;">char</span> <span style="color: #339933;">*</span><span style="color: #009900;">&#41;</span> emalloc<span style="color: #009900;">&#40;</span>Z_STRLEN_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #339933;">+</span> <span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #000066;">memcpy</span><span style="color: #009900;">&#40;</span>Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> Z_STRVAL_P<span style="color: #009900;">&#40;</span>op1<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op1<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #000066;">memcpy</span><span style="color: #009900;">&#40;</span>Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">+</span>Z_STRLEN_P<span style="color: #009900;">&#40;</span>op1<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> Z_STRVAL_P<span style="color: #009900;">&#40;</span>op2<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op2<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span>Z_STRLEN_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #0000dd;">0</span><span style="color: #339933;">;</span>
	Z_TYPE_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #339933;">=</span> IS_STRING<span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>示例执行了两次连接操作，则执行了两次内存分配操作和四次拷贝操作。</p>
<p>而直接在字符串中插入变量，其所有的操作都是添加操作，将字符串添加到返回值，将变量添加到返回值，<br />
所有的结果返回都是在一个临时变量中，如我们的示例，首先会将&#8221;test string begin &#8220;添加到临时变量，然后将临时变量和$var变量添加到临时变量，之后将临时变量和&#8221; end&#8221;添加到临时变量，最后将此此时变量赋值给$str。这里添加将字符串添加到临时变量，其对应的opcode为ZEND_ADD_STRING，将变量添加到临时变量，其对应的opcode为ZEND_ADD_VAR，虽然这两个操作的opcode不同，但其最终调用都是add_string_to_string，他们所不同的调用此函数的第三个参数，一个是操作码存储的ZVAL变量，一个是通过变更列表获取的ZVAL变量。<br />
其调用结构如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">// 添加字符串</span>
zval <span style="color: #339933;">*</span>str <span style="color: #339933;">=</span> <span style="color: #339933;">&amp;</span>EX_T<span style="color: #009900;">&#40;</span>opline<span style="color: #339933;">-&gt;</span>result.<span style="color: #202020;">u</span>.<span style="color: #202020;">var</span><span style="color: #009900;">&#41;</span>.<span style="color: #202020;">tmp_var</span><span style="color: #339933;">;</span>
add_string_to_string<span style="color: #009900;">&#40;</span>str<span style="color: #339933;">,</span> str<span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>opline<span style="color: #339933;">-&gt;</span>op2.<span style="color: #202020;">u</span>.<span style="color: #202020;">constant</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">//添加变量</span>
zval <span style="color: #339933;">*</span>str <span style="color: #339933;">=</span> <span style="color: #339933;">&amp;</span>EX_T<span style="color: #009900;">&#40;</span>opline<span style="color: #339933;">-&gt;</span>result.<span style="color: #202020;">u</span>.<span style="color: #202020;">var</span><span style="color: #009900;">&#41;</span>.<span style="color: #202020;">tmp_var</span><span style="color: #339933;">;</span>
zval <span style="color: #339933;">*</span>var <span style="color: #339933;">=</span> _get_zval_ptr_tmp<span style="color: #009900;">&#40;</span><span style="color: #339933;">&amp;</span>opline<span style="color: #339933;">-&gt;</span>op2<span style="color: #339933;">,</span> EX<span style="color: #009900;">&#40;</span>Ts<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>free_op2 TSRMLS_CC<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
add_string_to_string<span style="color: #009900;">&#40;</span>str<span style="color: #339933;">,</span> str<span style="color: #339933;">,</span> var<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>在添加变量时，如果添加的变量不是字符串，会通过zend_make_printable_zval将变量转换成字符串输出，如数组会转换成Array。<br />
add_string_to_string的实现在Zend/zend_operators.c文件中：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;"><span style="color: #808080; font-style: italic;">/* must support result==op1 */</span>
ZEND_API <span style="color: #993333;">int</span> add_string_to_string<span style="color: #009900;">&#40;</span>zval <span style="color: #339933;">*</span>result<span style="color: #339933;">,</span> <span style="color: #993333;">const</span> zval <span style="color: #339933;">*</span>op1<span style="color: #339933;">,</span> <span style="color: #993333;">const</span> zval <span style="color: #339933;">*</span>op2<span style="color: #009900;">&#41;</span> <span style="color: #808080; font-style: italic;">/* {{{ */</span>
<span style="color: #009900;">&#123;</span>
	<span style="color: #993333;">int</span> length <span style="color: #339933;">=</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op1<span style="color: #009900;">&#41;</span> <span style="color: #339933;">+</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op2<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
	Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #339933;">=</span> <span style="color: #009900;">&#40;</span><span style="color: #993333;">char</span> <span style="color: #339933;">*</span><span style="color: #009900;">&#41;</span> erealloc<span style="color: #009900;">&#40;</span>Z_STRVAL_P<span style="color: #009900;">&#40;</span>op1<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> length<span style="color: #339933;">+</span><span style="color: #0000dd;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	<span style="color: #000066;">memcpy</span><span style="color: #009900;">&#40;</span>Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #339933;">+</span>Z_STRLEN_P<span style="color: #009900;">&#40;</span>op1<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> Z_STRVAL_P<span style="color: #009900;">&#40;</span>op2<span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> Z_STRLEN_P<span style="color: #009900;">&#40;</span>op2<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
	Z_STRVAL_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#91;</span>length<span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #0000dd;">0</span><span style="color: #339933;">;</span>
	Z_STRLEN_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #339933;">=</span> length<span style="color: #339933;">;</span>
	Z_TYPE_P<span style="color: #009900;">&#40;</span>result<span style="color: #009900;">&#41;</span> <span style="color: #339933;">=</span> IS_STRING<span style="color: #339933;">;</span>
	<span style="color: #b1b100;">return</span> SUCCESS<span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
<span style="color: #808080; font-style: italic;">/* }}} */</span></pre></td></tr></table></div>

<p>add_string_to_string函数的实现过程是针对即将生成的字符串的大小重新通过PHP内核的内存管理扩展内存空间（如果当前空间后续的内存够用，则天下太平，如果空间不够，则重新分配空间并执行拷贝操作），并将新的字符串复制到原始字串后面内存空间的过程。<br />
我们的示例执行了三次添加操作，也就执行了三次内存扩展操作和三次拷贝操作。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2012/03/php-string-concat/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>如何在用户中断时停止程序的运行</title>
		<link>https://www.phppan.com/2012/02/stop-program-by-user-abort/</link>
		<comments>https://www.phppan.com/2012/02/stop-program-by-user-abort/#comments</comments>
		<pubDate>Mon, 27 Feb 2012 00:59:34 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[connection_status]]></category>
		<category><![CDATA[ignore_user_abort]]></category>
		<category><![CDATA[PHP源码]]></category>
		<category><![CDATA[PHP源码阅读笔记]]></category>
		<category><![CDATA[中断]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1612</guid>
		<description><![CDATA[当我们以WEB的方式运行PHP脚本时，默认情况下，即使你关闭当前页面，程序也会继续执行，直接程序结束或超时。如 [&#8230;]]]></description>
				<content:encoded><![CDATA[<p>当我们以WEB的方式运行PHP脚本时，默认情况下，即使你关闭当前页面，程序也会继续执行，直接程序结束或超时。如果我们想在用户关闭页面或点击了停止页面运行时就中断程序，我们需要做些什么呢？上周和小毅同学讨论了这个问题，从而也引出了今天我们这篇文章。</p>
<p>我们知道HTTP协议是基于TCP/IP协议，对于一个PHP页面的请求就是一个HTTP请求（假设我们是Apache服务器），从而会创建TCP连接，当用户中断请求时，会给服务器一个abort状态。这个abort状态就是今天我们要讲的关键点。</p>
<style>
.entry p {margin:13px 5px 0 5px;}
</style>
<p>在PHP中有一个函数与abort状态有关：ignore_user_abort函数<br />
ignore_user_abort() 函数设置与客户机断开时是否会终止脚本的执行。它返回 user-abort 之前设置的布尔值。它的参数可选。如果设置为 true，则忽略与用户的断开，如果设置为 false，会导致脚本停止运行。</p>
<p>PHP 不会检测到用户是否已断开连接，直到尝试向客户机发送信息为止。因此如果我们只是使用echo语句，可能无法如实的看到abort的效果，因为PHP在输出时会有一个缓存，如果要刷新缓存，则可以使用flush() 函数。</p>
<p>如下代码t.php：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #990000;">ignore_user_abort</span><span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">TRUE</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">set_time_limit</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">50</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #b1b100;">while</span> <span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #b1b100;">echo</span> <span style="color: #000088;">$i</span><span style="color: #339933;">++,</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>    
    <span style="color: #990000;">flush</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #000088;">$fp</span> <span style="color: #339933;">=</span> <span style="color: #990000;">fopen</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;data.txt&quot;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'a'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #990000;">fwrite</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$fp</span><span style="color: #339933;">,</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot; <span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #990000;">fclose</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$fp</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #990000;">sleep</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>在浏览器中执行这段代码，过了大概两秒后，关闭请求的页面，50秒后，你会发现在data.txt文件中写入了至少50个数。这表示我们的中断操作是无效的。<br />
如果我们改一下，把第一句改为：ignore_user_abort(FALSE);，重复上面的操作，你会发现，只写入了极少的数字，这表示我们的中断操作有效了。<br />
现在通过ignore_user_abort函数，我们实现了用户中断就马上停止程序的操作。这里有一个问题，即我们需要不停的flush，通过flush函数来更新连接状态，当状态为abort时，程序根据ignore_user_abort的设置来判断是否中断程序。除此之外，我们也可以使用直接获取连接状态来check连接状态，并对特定的状态作出处理，如下代码：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="php" style="font-family:monospace;"><span style="color: #990000;">ignore_user_abort</span><span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">FALSE</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #990000;">set_time_limit</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">50</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #b1b100;">while</span> <span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
    <span style="color: #b1b100;">echo</span> <span style="color: #000088;">$i</span><span style="color: #339933;">++,</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
    <span style="color: #990000;">flush</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
     <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #990000;">connection_status</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">!=</span> CONNECTION_NORMAL<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #b1b100;">break</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #000088;">$fp</span> <span style="color: #339933;">=</span> <span style="color: #990000;">fopen</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;data.txt&quot;</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'a'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #990000;">fwrite</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$fp</span><span style="color: #339933;">,</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;:&quot;</span> <span style="color: #339933;">.</span> <span style="color: #990000;">connection_status</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot; <span style="color: #000099; font-weight: bold;">\r</span><span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #990000;">fclose</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$fp</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #990000;">sleep</span><span style="color: #009900;">&#40;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></td></tr></table></div>

<p>这里的connection_status函数的作用是获取连接的状态，当连接的状态非normal时，我们就中断循环，从而也达到了中断程序的操作。这个示例与前面的示例不同之处在于中断操作是由我们自己控制，而不是通过flush操作直接exit。如果在用户中断后还有一些其它的操作，这种方式会更合适一些。当然，这里的flush操作依旧不可少，我们还是需要通过这个函数做check操作。</p>
<p>ignore_user_abort函数和connection_status函数都实现了我们的目的，这两个函数的实现有没有关联？我们在ext/standard/basic_functions.c文件中找到这两个函数的实现如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;"><span style="color: #808080; font-style: italic;">/* {{{ proto int connection_aborted(void)
&nbsp;
Returns true if client disconnected */</span>
PHP_FUNCTION<span style="color: #009900;">&#40;</span>connection_aborted<span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    RETURN_LONG<span style="color: #009900;">&#40;</span>PG<span style="color: #009900;">&#40;</span>connection_status<span style="color: #009900;">&#41;</span> <span style="color: #339933;">&amp;</span> PHP_CONNECTION_ABORTED<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
<span style="color: #808080; font-style: italic;">/* }}} */</span>
&nbsp;
<span style="color: #808080; font-style: italic;">/* {{{ proto int connection_status(void)
Returns the connection status bitfield */</span>
PHP_FUNCTION<span style="color: #009900;">&#40;</span>connection_status<span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    RETURN_LONG<span style="color: #009900;">&#40;</span>PG<span style="color: #009900;">&#40;</span>connection_status<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
<span style="color: #808080; font-style: italic;">/* }}} */</span>
&nbsp;
<span style="color: #808080; font-style: italic;">/* {{{ proto int ignore_user_abort([string value])
Set whether we want to ignore a user abort event or not */</span>
PHP_FUNCTION<span style="color: #009900;">&#40;</span>ignore_user_abort<span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    <span style="color: #993333;">char</span> <span style="color: #339933;">*</span>arg <span style="color: #339933;">=</span> NULL<span style="color: #339933;">;</span>
    <span style="color: #993333;">int</span> arg_len <span style="color: #339933;">=</span> <span style="color: #0000dd;">0</span><span style="color: #339933;">;</span>
    <span style="color: #993333;">int</span> old_setting<span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span>zend_parse_parameters<span style="color: #009900;">&#40;</span>ZEND_NUM_ARGS<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> TSRMLS_CC<span style="color: #339933;">,</span> <span style="color: #ff0000;">&quot;|s&quot;</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>arg<span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span>arg_len<span style="color: #009900;">&#41;</span> <span style="color: #339933;">==</span> FAILURE<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        <span style="color: #b1b100;">return</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    old_setting <span style="color: #339933;">=</span> PG<span style="color: #009900;">&#40;</span>ignore_user_abort<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span>arg<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        zend_alter_ini_entry_ex<span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;ignore_user_abort&quot;</span><span style="color: #339933;">,</span> <span style="color: #993333;">sizeof</span><span style="color: #009900;">&#40;</span><span style="color: #ff0000;">&quot;ignore_user_abort&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span> arg<span style="color: #339933;">,</span> arg_len<span style="color: #339933;">,</span> PHP_INI_USER<span style="color: #339933;">,</span>     PHP_INI_STAGE_RUNTIME<span style="color: #339933;">,</span> <span style="color: #0000dd;">0</span> TSRMLS_CC<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    RETURN_LONG<span style="color: #009900;">&#40;</span>old_setting<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
<span style="color: #808080; font-style: italic;">/* }}} */</span></pre></td></tr></table></div>

<p>connection_status函数直接返回PG(connection_status)的值，</p>
<p>ignore_user_abort函数重新设置PG(ignore_user_abort)的值，</p>
<p>不管是因为缓存满自动调用或通过flush函数调用的flush操作，其最终都会根据连接状态判断是否执行php_handle_aborted_connection函数，如果是abort状态，则执行。</p>
<p>其代码如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;"><span style="color: #808080; font-style: italic;">/* {{{ php_handle_aborted_connection
*/</span>
PHPAPI <span style="color: #993333;">void</span> php_handle_aborted_connection<span style="color: #009900;">&#40;</span><span style="color: #993333;">void</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
    TSRMLS_FETCH<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    PG<span style="color: #009900;">&#40;</span>connection_status<span style="color: #009900;">&#41;</span> <span style="color: #339933;">=</span> PHP_CONNECTION_ABORTED<span style="color: #339933;">;</span>
    php_output_set_status<span style="color: #009900;">&#40;</span><span style="color: #0000dd;">0</span> TSRMLS_CC<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #b1b100;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span>PG<span style="color: #009900;">&#40;</span>ignore_user_abort<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
        zend_bailout<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span>
<span style="color: #808080; font-style: italic;">/* }}} */</span></pre></td></tr></table></div>

<p>在PG(ignore_user_abort)为假时，即不忽略用户的中断行为时，如果调用了此函数，则使用zend_bailout函数跳出程序直接exit。</p>
<p>在默认情况下ignore_user_abort为0，即不忽略用户的中断行为。</p>
<p>如果你是ubuntu的默认apache环境下，可能上面的代码会无效。这是由于此环境下的apache开启了zip，在没有达到预定的大小时，服务器不会与客户端通信，从而也就无法获取客户端的状态，即使使用了flush函数也是一样。</p>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2012/02/stop-program-by-user-abort/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
