<?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="http://www.biaodianfu.com/category/web-development/programming/php/feed" rel="self" type="application/rss+xml" />
	<link>http://www.biaodianfu.com</link>
	<description>编译自己的互联网生活</description>
	<lastBuildDate>Wed, 08 Feb 2012 08:42:34 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>【教程】使用PHP开发自己的MVC框架</title>
		<link>http://www.biaodianfu.com/write-your-own-php-mvc-framework.html</link>
		<comments>http://www.biaodianfu.com/write-your-own-php-mvc-framework.html#comments</comments>
		<pubDate>Sun, 11 Dec 2011 08:27:31 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[MVC]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=4452</guid>
		<description><![CDATA[一、什么是MVC MVC模式（Model-View-Controller）是软件工程中的一种软件架构模式，把软件系统分为三个基本部分：模型（Model）、视图（View）和控制器（Controller）。 MVC模式的目的是实现一种动态的程序设计，使后续对程序的修改和扩展简化，并且使程序某一部分的重复利用成为可能。除此之外，此模式通过对复杂度的简化，使程序结构更加直观。软件系统通过对自身基本部份分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组： （控制器Controller）- 负责转发请求，对请求进行处理。 （视图View） &#8211; 界面设计人员进行图形界面设计。 （模型Model） &#8211; 程序员编写程序应有的功能（实现算法等等）、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。 模型（Model） “数据模型”（Model）用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力，例如对数据库的访问。“模型”不依赖“视图”和“控制器”，也就是说，模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制，那些用于监视此模型的视图必须事先在此模型上注册，从而，视图可以了解在数据模型上发生的改变。 视图（View） 视图层能够实现数据有目的的显示（理论上，这不是必需的）。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能，视图需要访问它监视的数据模型（Model），因此应该事先在被它监视的数据那里注册。 控制器（Controller） 控制器起到不同层面间的组织作用，用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变。 二、为什么要自己开发MVC框架 网络上有大量优秀的MVC框架可供使用，本教程并不是为了开发一个全面的、终极的MVC框架解决方案，而是将它看作是一个很好的从内部学习PHP的机会，在此过程中，你将学习面向对象编程和设计模式，并学习到开放中的一些注意事项。 更重要的是，你可以完全控制你的框架，并将你的想法融入到你开发的框架中。虽然不一定是做好的，但是你可以按照你的方式去开发功能和模块。 三、开始开发自己的MVC框架 在开始开发前，让我们先来把项目建立好，假设我们建立的项目为todo，那么接下来的第一步就是把目录结构先设置好。 虽然在这个教程中不会使用到上面的所有的目录，但是为了以后程序的可拓展性，在一开始就把程序目录设置好使非常必要的。下面就具体说说每个目录的作用： application – 存放程序代码 config – 存放程序配置或数据库配置 db – 用来存放数据库备份内容 library – 存放框架代码 public – 存放静态文件 scripts – 存放命令行工具 tmp – 存放临时数据 在目录设置好以后，我们接下来就要来顶一下一些代码的规范： MySQL的表名需小写并采用复数形式，如items,cars 模块名（Models）需首字母大写，并采用单数模式，如Item,Car 控制器（Controllers）需首字母大写，采用复数形式并在名称中添加“Controller”，如ItemsController, CarsController 视图（Views）采用复数形式，并在后面添加行为作为文件，如：items/view.php, cars/buy.php 上述的一些规则是为了能在程序钟更好的进行互相的调用。接下来就开始真正的编码了。 第一步将所有的的请求都重定向到public目录下，解决方案是在todo文件下添加一个.htaccesss文件，文件内容为： &#60;IfModule mod_rewrite.c&#62; [...]]]></description>
			<content:encoded><![CDATA[<p><strong>一、什么是MVC</strong></p>
<p><strong>MVC</strong><strong>模式</strong>（Model-View-Controller）是软件工程中的一种软件架构模式，把软件系统分为三个基本部分：模型（Model）、视图（View）和控制器（Controller）。</p>
<p><strong>MVC</strong><strong>模式</strong>的目的是实现一种动态的程序设计，使后续对程序的修改和扩展简化，并且使程序某一部分的重复利用成为可能。除此之外，此模式通过对复杂度的简化，使程序结构更加直观。软件系统通过对自身基本部份分离的同时也赋予了各个基本部分应有的功能。专业人员可以通过自身的专长分组：</p>
<ul>
<li>（控制器Controller）- 负责转发请求，对请求进行处理。</li>
<li>（视图View） &#8211; 界面设计人员进行图形界面设计。</li>
<li>（模型Model） &#8211; 程序员编写程序应有的功能（实现算法等等）、数据库专家进行数据管理和数据库设计(可以实现具体的功能)。</li>
</ul>
<p><img class="alignnone size-full wp-image-4453" title="mvc" src="http://www.biaodianfu.com/wp-content/uploads/2011/11/mvc.png" alt="" width="321" height="151" /><br />
<strong>模型（Model）</strong> “数据模型”（Model）用于封装与应用程序的业务逻辑相关的数据以及对数据的处理方法。“模型”有对数据直接访问的权力，例如对数据库的访问。“模型”不依赖“视图”和“控制器”，也就是说，模型不关心它会被如何显示或是如何被操作。但是模型中数据的变化一般会通过一种刷新机制被公布。为了实现这种机制，那些用于监视此模型的视图必须事先在此模型上注册，从而，视图可以了解在数据模型上发生的改变。</p>
<p><strong>视图（View）</strong> 视图层能够实现数据有目的的显示（理论上，这不是必需的）。在视图中一般没有程序上的逻辑。为了实现视图上的刷新功能，视图需要访问它监视的数据模型（Model），因此应该事先在被它监视的数据那里注册。</p>
<p><strong>控制器（Controller）</strong> 控制器起到不同层面间的组织作用，用于控制应用程序的流程。它处理事件并作出响应。“事件”包括用户的行为和数据模型上的改变。</p>
<p><strong>二、为什么要自己开发MVC框架</strong></p>
<p>网络上有大量优秀的MVC框架可供使用，本教程并不是为了开发一个全面的、终极的MVC框架解决方案，而是将它看作是一个很好的从内部学习PHP的机会，在此过程中，你将学习面向对象编程和设计模式，并学习到开放中的一些注意事项。</p>
<p>更重要的是，你可以完全控制你的框架，并将你的想法融入到你开发的框架中。虽然不一定是做好的，但是你可以按照你的方式去开发功能和模块。</p>
<p><strong>三、开始开发自己的MVC框架</strong></p>
<p>在开始开发前，让我们先来把项目建立好，假设我们建立的项目为todo，那么接下来的第一步就是把目录结构先设置好。</p>
<p><img class="alignnone size-full wp-image-4533" title="todo" src="http://www.biaodianfu.com/wp-content/uploads/2011/12/todo.png" alt="" width="217" height="264" /></p>
<p>虽然在这个教程中不会使用到上面的所有的目录，但是为了以后程序的可拓展性，在一开始就把程序目录设置好使非常必要的。下面就具体说说每个目录的作用：</p>
<ul>
<li>application – 存放程序代码</li>
<li>config – 存放程序配置或数据库配置</li>
<li>db – 用来存放数据库备份内容</li>
<li>library – 存放框架代码</li>
<li>public – 存放静态文件</li>
<li>scripts – 存放命令行工具</li>
<li>tmp – 存放临时数据</li>
</ul>
<p>在目录设置好以后，我们接下来就要来顶一下一些代码的规范：</p>
<ol>
<li>MySQL的表名需小写并采用复数形式，如items,cars</li>
<li>模块名（Models）需首字母大写，并采用单数模式，如Item,Car</li>
<li>控制器（Controllers）需首字母大写，采用复数形式并在名称中添加“Controller”，如ItemsController, CarsController</li>
<li>视图（Views）采用复数形式，并在后面添加行为作为文件，如：items/view.php, cars/buy.php</li>
</ol>
<p>上述的一些规则是为了能在程序钟更好的进行互相的调用。接下来就开始真正的编码了。</p>
<p>第一步将所有的的请求都重定向到public目录下，解决方案是在todo文件下添加一个.htaccesss文件，文件内容为：</p>
<pre>&lt;IfModule mod_rewrite.c&gt;
RewriteEngine on
RewriteRule    ^$    public/    [L]
RewriteRule    (.*) public/$1    [L]
&lt;/IfModule&gt;</pre>
<p>在我们把所有的请求都重定向到public目录下以后，我们就需要将所有的数据请求都再重定向到public下的index.php文件，于是就需要在public文件夹下也新建一个.htaccess文件，文件内容为：</p>
<pre>&lt;IfModule mod_rewrite.c&gt;
RewriteEngine On
#如果文件存在就直接访问目录不进行RewriteRule
RewriteCond %{REQUEST_FILENAME} !-f
#如果目录存在就直接访问目录不进行RewriteRule
RewriteCond %{REQUEST_FILENAME} !-d
#将所有其他URL重写到 index.php/URL
RewriteRule ^(.*)$ index.php?url=$1 [PT,L]
&lt;/IfModule&gt;</pre>
<p>这么做的主要原因有：</p>
<ol>
<li>可以使程序有一个单一的入口，将所有除静态程序以外的程序都重定向到index.php上；</li>
<li>可以用来生成利于SEO的URL，想要更好的配置URL，后期可能会需要URL路由，这里先不做介绍了。</li>
</ol>
<p>做完上面的操作，就应该知道我们需要做什么了，没错！在public目录下添加index.php文件，文件内容为：</p>
<pre class="brush: php; gutter: true">&lt;?php
define('DS',DIRECTORY_SEPARATOR);
define('ROOT',dirname(dirname(__FILE__)));
$url = $_GET['url'];
require_once(ROOT.DS.'library'.DS.'bootstrap.php');</pre>
<p>注意上面的PHP代码中，并没有添加PHP结束符号”?&gt;”，这么做的主要原因是：对于只包含PHP代码的文件，结束标志(“?&gt;”)最好不存在，PHP自身并不需要结束符号，不添加结束符号可以很大程度上防止末尾被添加额外的注入内容，让程序更加安全。</p>
<p>在index.php中，我们对library文件夹下的bootstrap.php发起了请求，那么bootstrap.php这个启动文件中到底会包含哪些内容呢？</p>
<pre class="brush: php; gutter: true">&lt;?php
require_once(ROOT.DS.'config'.DS .'config.php');
require_once(ROOT.DS.'library'.DS .'shared.php');</pre>
<p>以上文件都可以直接在index.php文件中引用，我们这么做的原因是为了在后期管理和拓展中更加的方便，所以把需要在一开始的时候就加载运行的程序统一放到一个单独的文件中引用。</p>
<p>先来看看config文件下的config .php文件，该文件的主要作用是设置一些程序的配置项及数据库连接等，主要内容为：</p>
<pre class="brush: php; gutter: true">&lt;?php
 # 设置是否为开发状态
 define('DEVELOPMENT_ENVIRONMENT',true);
 # 设置数据库连接所需数据
 define('DB_HOST','localhost');
 define('DB_NAME','todo');
 define('DB_USER','root');
 define('DB_PASSWORD','root');</pre>
<p>应该说config.php涉及到的内容并不多，不过是一些基础数据的一些设置，再来看看library下的共用文件shared.php应该怎么写。</p>
<pre class="brush: php; gutter: true">&lt;?php
/* 检查是否为开发环境并设置是否记录错误日志 */
function setReporting(){
    if (DEVELOPMENT_ENVIRONMENT == true) {
        error_reporting(E_ALL);
        ini_set('display_errors','On');
    } else {
        error_reporting(E_ALL);
        ini_set('display_errors','Off');
        ini_set('log_errors','On');
        ini_set('error_log',ROOT.DS. 'tmp' .DS. 'logs' .DS. 'error.log');
    }
}

/* 检测敏感字符转义（Magic Quotes）并移除他们 */
function stripSlashDeep($value){
    $value = is_array($value) ? array_map('stripSlashDeep',$value) : stripslashes($value);
    return $value;
}
function removeMagicQuotes(){
    if (get_magic_quotes_gpc()) {
        $_GET = stripSlashDeep($_GET);
        $_POST = stripSlashDeep($_POST);
        $_COOKIE = stripSlashDeep($_COOKIE);
    }
}

/* 检测全局变量设置（register globals）并移除他们 */
function unregisterGlobals(){
    if (ini_get('register_globals')) {
        $array = array('_SESSION','_POST','_GET','_COOKIE','_REQUEST','_SERVER','_ENV','_FILES');
        foreach ($array as $value) {
            foreach ($GLOBALS[$value] as $key =&gt; $var) {
                if ($var === $GLOBALS[$key]) {
                    unset($GLOBALS[$key]);
                }
            }
        }
    }
}

/* 主请求方法，主要目的拆分URL请求 */
function callHook() {
    global $url;
    $urlArray = array();
    $urlArray = explode("/",$url);
    $controller = $urlArray[0];
    array_shift($urlArray);
    $action = $urlArray[0];
    array_shift($urlArray);
    $queryString = $urlArray;
    $controllerName = $controller;
    $controller = ucwords($controller);
    $model = rtrim($controller, 's');
    $controller .= 'Controller';
    $dispatch = new $controller($model,$controllerName,$action);
    if ((int)method_exists($controller, $action)) {
        call_user_func_array(array($dispatch,$action),$queryString);
    } else {
        /* 生成错误代码 */
    }
}

/* 自动加载控制器和模型 */
function __autoload($className) {
    if (file_exists(ROOT . DS . 'library' . DS . strtolower($className) . '.class.php')) {
        require_once(ROOT . DS . 'library' . DS . strtolower($className) . '.class.php');
    } else if (file_exists(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php')) {
        require_once(ROOT . DS . 'application' . DS . 'controllers' . DS . strtolower($className) . '.php');
    } else if (file_exists(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '.php')) {
        require_once(ROOT . DS . 'application' . DS . 'models' . DS . strtolower($className) . '.php');
    } else {
           /* 生成错误代码 */
    }
}

setReporting();
removeMagicQuotes();
unregisterGlobals();
callHook();</pre>
<p>接下来的操作就是在library中建立程序所需要的基类，包括控制器、模型和视图的基类。</p>
<p>新建控制器基类为controller.class.php，控制器的主要功能就是总调度，具体具体内容如下：</p>
<pre class="brush: php; gutter: true">&lt;?php
 class Controller {
 protected $_model;
 protected $_controller;
 protected $_action;
 protected $_template;
function __construct($model, $controller,$action) {
 $this-&gt;_controller = $controller;
 $this-&gt;_action = $action;
 $this-&gt;_model = $model;
$this-&gt;$model =&amp; new $model;
 $this-&gt;_template =&amp; new Template($controller,$action);
 }
function set($name,$value) {
 $this-&gt;_template-&gt;set($name,$value);
 }
function __destruct() {
 $this-&gt;_template-&gt;render();
 }
 }</pre>
<p>新建控制器基类为model.class.php，考虑到模型需要对数据库进行处理，所以可以新建一个数据库基类sqlquery.class.php，模型去继承sqlquery.class.php。</p>
<p>新建sqlquery.class.php，代码如下：</p>
<pre class="brush: php; gutter: true">&lt;?php
 class SQLQuery {
 protected $_dbHandle;
 protected $_result;
/** 连接数据库 **/
 function connect($address, $account, $pwd, $name) {
 $this-&gt;_dbHandle = @mysql_connect($address, $account, $pwd);
 if ($this-&gt;_dbHandle != 0) {
 if (mysql_select_db($name, $this-&gt;_dbHandle)) {
 return 1;
 }
 else {
 return 0;
 }
 }
 else {
 return 0;
 }
 }
/** 中断数据库连接 **/
 function disconnect() {
 if (@mysql_close($this-&gt;_dbHandle) != 0) {
 return 1;
 }  else {
 return 0;
 }
 }
/** 查询所有数据表内容 **/
 function selectAll() {
 $query = 'select * from `'.$this-&gt;_table.'`';
 return $this-&gt;query($query);
 }
/** 查询数据表指定列内容 **/
 function select($id) {
 $query = 'select * from `'.$this-&gt;_table.'` where `id` = \''.mysql_real_escape_string($id).'\'';
 return $this-&gt;query($query, 1);
 }
/** 自定义SQL查询语句 **/
 function query($query, $singleResult = 0) {
 $this-&gt;_result = mysql_query($query, $this-&gt;_dbHandle);
 if (preg_match("/select/i",$query)) {
 $result = array();
 $table = array();
 $field = array();
 $tempResults = array();
 $numOfFields = mysql_num_fields($this-&gt;_result);
 for ($i = 0; $i &lt; $numOfFields; ++$i) {
 array_push($table,mysql_field_table($this-&gt;_result, $i));
 array_push($field,mysql_field_name($this-&gt;_result, $i));
 }
 while ($row = mysql_fetch_row($this-&gt;_result)) {
 for ($i = 0;$i &lt; $numOfFields; ++$i) {
 $table[$i] = trim(ucfirst($table[$i]),"s");
 $tempResults[$table[$i]][$field[$i]] = $row[$i];
 }
 if ($singleResult == 1) {
 mysql_free_result($this-&gt;_result);
 return $tempResults;
 }
 array_push($result,$tempResults);
 }
 mysql_free_result($this-&gt;_result);
 return($result);
 }
 }
/** 返回结果集行数 **/
 function getNumRows() {
 return mysql_num_rows($this-&gt;_result);
 }
/** 释放结果集内存 **/
 function freeResult() {
 mysql_free_result($this-&gt;_result);
 }
/** 返回MySQL操作错误信息 **/
 function getError() {
 return mysql_error($this-&gt;_dbHandle);
 }
 }</pre>
<p>新建model.class.php，代码如下：</p>
<pre class="brush: php; gutter: true">&lt;?php
 class Model extends SQLQuery{
 protected $_model;
function __construct() {
 $this-&gt;connect(DB_HOST,DB_USER,DB_PASSWORD,DB_NAME);
 $this-&gt;_model = get_class($this);
 $this-&gt;_table = strtolower($this-&gt;_model)."s";
 }
function __destruct() {
 }
 }</pre>
<p>新建视图基类为template.class.php，具体代码如下：</p>
<pre class="brush: php; gutter: true">&lt;?php
 class Template {
 protected $variables = array();
 protected $_controller;
 protected $_action;
function __construct($controller,$action) {
 $this-&gt;_controller = $controller;
 $this-&gt;_action =$action;
 }
/* 设置变量 */
 function set($name,$value) {
 $this-&gt;variables[$name] = $value;
 }
/* 显示模板 */
 function render() {
 extract($this-&gt;variables);
 if (file_exists(ROOT.DS. 'application' .DS. 'views' .DS. $this-&gt;_controller .DS. 'header.php')) {
 include(ROOT.DS. 'application' .DS. 'views' .DS. $this-&gt;_controller .DS. 'header.php');
 } else {
 include(ROOT.DS. 'application' .DS. 'views' .DS. 'header.php');
 }
 include (ROOT.DS. 'application' .DS. 'views' .DS. $this-&gt;_controller .DS. $this-&gt;_action . '.php');
 if (file_exists(ROOT.DS. 'application' .DS. 'views' .DS. $this-&gt;_controller .DS. 'footer.php')) {
 include (ROOT.DS. 'application' .DS. 'views' .DS. $this-&gt;_controller .DS. 'footer.php');
 } else {
 include (ROOT.DS. 'application' .DS. 'views' .DS. 'footer.php');
 }
 }
 }</pre>
<p>做完了以上这么多操作，基本上整个MVC框架已经出来了，下面就该制作我们的站点了。我们要做的站点其实很简单，一个ToDo程序。</p>
<p>首先是在我们的/application/controller/ 目录下面新建一个站点控制器类为ItemsController，命名为itemscontroller.php，内容为：</p>
<pre class="brush: php; gutter: true">&lt;?php
 class ItemsController extends Controller {
 function view($id = null,$name = null) {
 $this-&gt;set('title',$name.' - My Todo List App');
 $this-&gt;set('todo',$this-&gt;Item-&gt;select($id));
 }
function viewall() {
 $this-&gt;set('title','All Items - My Todo List App');
 $this-&gt;set('todo',$this-&gt;Item-&gt;selectAll());
 }
function add() {
 $todo = $_POST['todo'];
 $this-&gt;set('title','Success - My Todo List App');
 $this-&gt;set('todo',$this-&gt;Item-&gt;query('insert into items (item_name) values (\''.mysql_real_escape_string($todo).'\')'));
 }
function delete($id) {
 $this-&gt;set('title','Success - My Todo List App');
 $this-&gt;set('todo',$this-&gt;Item-&gt;query('delete from items where id = \''.mysql_real_escape_string($id).'\''));
 }
 }</pre>
<p>接下来就是先建站点的模型，在我们的/application/model/ 目录下面先建一个站点模型类为Item，内容直接继承Model，代码如下：</p>
<pre class="brush: php; gutter: true">&lt;?php
 class Item extends Model {
}</pre>
<p>最后一步是设置我们站点的视图部分，我们现在/application/views/目录下新建一个items的文件夹，再在items文件夹下建立与控制器重Action相同的文件，分别为view.php，viewall.php，add.php，delete.php，考虑到这么页面中可能需要共用页首和页尾，所以再新建两个文件，命名为header.php，footer.php，每个文件的代码如下：</p>
<p>view.php文件：查看单条待处理事务</p>
<pre class="brush: php; gutter: true">&lt;h2&gt;&lt;?php echo $todo['Item']['item_name']?&gt;&lt;/h2&gt;
 &lt;a href="../../../items/delete/&lt;?php echo $todo['Item']['id']?&gt;"&gt;
 &lt;span&gt;Delete this item&lt;/span&gt;
 &lt;/a&gt;</pre>
<p>viewall.php文件：查看所有待处理事务</p>
<pre class="brush: php; gutter: true">&lt;form action="../items/add" method="post"&gt;
 &lt;input type="text" value="I have to..." onclick="this.value=''" name="todo"&gt; &lt;input type="submit" value="add"&gt;
 &lt;/form&gt;
 &lt;br/&gt;&lt;br/&gt;
 &lt;?php $number = 0?&gt;
&lt;?php foreach ($todo as $todoitem):?&gt;
 &lt;a href="../items/view/&lt;?php echo $todoitem['Item']['id']?&gt;/&lt;?php echo strtolower(str_replace(" ","-",$todoitem['Item']['item_name']))?&gt;"&gt;
 &lt;span&gt;
 &lt;?php echo ++$number?&gt;
 &lt;?php echo $todoitem['Item']['item_name']?&gt;
 &lt;/span&gt;
 &lt;/a&gt;&lt;br/&gt;
 &lt;?php endforeach?&gt;</pre>
<p>add.php文件：添加待处理事务</p>
<pre class="brush: php; gutter: true">&lt;a href="../items/viewall"&gt;Todo successfully added. Click here to go back.&lt;/a&gt;&lt;br/&gt;</pre>
<p>delete.php文件：删除事务</p>
<pre class="brush: php; gutter: true">&lt;a href="../../items/viewall"&gt;Todo successfully deleted. Click here to go back.&lt;/a&gt;&lt;br/&gt;</pre>
<p>header.php：页首文件</p>
<pre class="brush: php; gutter: true">&lt;html&gt;
 &lt;head&gt;
 &lt;title&gt;&lt;?php echo $title?&gt;&lt;/title&gt;
 &lt;style&gt;
 .item {width:400px;}
 input {color:#222222;font-family:georgia,times;font-size:24px;font-weight:normal;line-height:1.2em;color:black;}
 a {color:#222222;font-family:georgia,times;font-size:24px;font-weight:normal;line-height:1.2em;color:black;text-decoration:none;}
 a:hover {background-color:#BCFC3D;}
 h1 {color:#000000;font-size:41px;letter-spacing:-2px;line-height:1em;font-family:helvetica,arial,sans-serif;border-bottom:1px dotted #cccccc;}
 h2 {color:#000000;font-size:34px;letter-spacing:-2px;line-height:1em;font-family:helvetica,arial,sans-serif;}
 &lt;/style&gt;
 &lt;/head&gt;
 &lt;body&gt;
 &lt;h1&gt;My Todo-List App&lt;/h1&gt;</pre>
<p>footer.php：页尾文件</p>
<pre class="brush: php; gutter: true">&lt;/body&gt;
 &lt;/html&gt;</pre>
<p>当然还有一个必不可少的操作就是在数据中中建立一张表，具体代码如下：</p>
<pre class="brush: sql; gutter: true">CREATE TABLE IF NOT EXISTS `items` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `item_name` varchar(255) NOT NULL,
 PRIMARY KEY (`id`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=17 ;</pre>
<p>至此一个使用MVC开发的网站就开发完成了，你现在可以通过访问http://localhost/todo/items/viewall 查看新建的站点。</p>
<p>原文链接：<a href="http://anantgarg.com/2009/03/13/write-your-own-php-mvc-framework-part-1/">http://anantgarg.com/2009/03/13/write-your-own-php-mvc-framework-part-1/</a></p>
<p>Related posts:<ol>
<li><a href='http://www.biaodianfu.com/create-an-object-oriented-blog-using-php.html' rel='bookmark' title='使用PHP创建一个面向对象的博客'>使用PHP创建一个面向对象的博客</a></li>
<li><a href='http://www.biaodianfu.com/how-to-make-heatmap.html' rel='bookmark' title='网站点击热力图的技术实现'>网站点击热力图的技术实现</a></li>
<li><a href='http://www.biaodianfu.com/php-get-remote-pic.html' rel='bookmark' title='PHP自动保存文章中的外部图片'>PHP自动保存文章中的外部图片</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/write-your-own-php-mvc-framework.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP API 框架开发的学习</title>
		<link>http://www.biaodianfu.com/php-api-framework.html</link>
		<comments>http://www.biaodianfu.com/php-api-framework.html#comments</comments>
		<pubDate>Wed, 14 Sep 2011 15:38:03 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[API]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=4241</guid>
		<description><![CDATA[基于互联网的应用正变得越来越普及，在这个过程中，有更多的站点将自身的资源开放给开发者来调用。对外提供的API 调用使得站点之间的内容关联性更强，同时这些开放的平台也为用户、开发者和中小网站带来了更大的价值。 在开发API前，你需要的是给API设定一个框架，这个框架一定是要简单的且是容易扩展的。下面就是用就来看看如何使用PHP来创建一个API。 API框架需要的特性 面向对象和结构化的代码 可修改的URL结构 创建多个版本 使用Hook来扩展框架API功能 API可连接数据库表 可定义多种输出格式 选择方法请求类型（GET, POST, PUT, DELETE） API框架的组成部分 API Framework主要由下面三中类型元素组成： Services Hooks Parsers 在一个运行的API中，每种类型的元素都有其自己的任务。下面就来详细说说每一种元素。 通常的API请求URL如下： http://www.domain.com/api/version/service/method/param_name/param_key.extension Service Service Class是API请求的控制器。这个Class包含了API可以使用的method，所以Service Class会需要计算和处理数据。 为了使method可以被请求，你需要将method设置为公开（public），并为service method设置请求类型（GET, POST, PUT, DELETE）。 下面是一个Service Class的类，你可以设置默认的请求方式为GET，并且version method可以接受GET和POST请求。 &#60;?php class MyApi_Service_Helloworld extends Api_Service_IService{ public function __construct($api){ parent::__construct($api); // Set execute request methods $this-&#62;addAllowedMethod("execute", Api_Request::METHOD_POST); $this-&#62;addAllowedMethod("version", Api_Request::METHOD_POST); $this-&#62;addAllowedMethod("version", Api_Request::METHOD_GET); [...]]]></description>
			<content:encoded><![CDATA[<p>基于互联网的应用正变得越来越普及，在这个过程中，有更多的站点将自身的资源开放给开发者来调用。对外提供的API 调用使得站点之间的内容关联性更强，同时这些开放的平台也为用户、开发者和中小网站带来了更大的价值。</p>
<p>在开发API前，你需要的是给API设定一个框架，这个框架一定是要简单的且是容易扩展的。下面就是用就来看看如何使用PHP来创建一个API。</p>
<p><strong>API框架需要的特性</strong></p>
<ul>
<li>面向对象和结构化的代码</li>
<li>可修改的URL结构</li>
<li>创建多个版本</li>
<li>使用Hook来扩展框架API功能</li>
<li>API可连接数据库表</li>
<li>可定义多种输出格式</li>
<li>选择方法请求类型（GET, POST, PUT, DELETE）</li>
</ul>
<p><strong>API框架的组成部分</strong></p>
<p>API Framework主要由下面三中类型元素组成：</p>
<ul>
<li>Services</li>
<li>Hooks</li>
<li>Parsers</li>
</ul>
<p>在一个运行的API中，每种类型的元素都有其自己的任务。下面就来详细说说每一种元素。</p>
<p><a href="http://www.biaodianfu.com/wp-content/uploads/2011/09/api-framework.png"><img class="alignnone size-full wp-image-4347" title="api-framework" src="http://www.biaodianfu.com/wp-content/uploads/2011/09/api-framework.png" alt="" width="544" height="389" /></a></p>
<p>通常的API请求URL如下：</p>
<p><em>http://www.domain.com/api/version/service/method/param_name/param_key.extension</em></p>
<h4><strong>Service</strong></h4>
<p>Service Class是API请求的控制器。这个Class包含了API可以使用的method，所以Service Class会需要计算和处理数据。</p>
<p>为了使method可以被请求，你需要将method设置为公开（public），并为service method设置请求类型（GET, POST, PUT, DELETE）。</p>
<p>下面是一个Service Class的类，你可以设置默认的请求方式为GET，并且version method可以接受GET和POST请求。</p>
<pre>&lt;?php
class MyApi_Service_Helloworld extends Api_Service_IService{

public function __construct($api){
parent::__construct($api);

// Set execute request methods
$this-&gt;addAllowedMethod("execute", Api_Request::METHOD_POST);
$this-&gt;addAllowedMethod("version", Api_Request::METHOD_POST);
$this-&gt;addAllowedMethod("version", Api_Request::METHOD_GET);

}

public function execute($params, $config){
$this-&gt;code = 200;
return "Hello world";
}

public function version($params, $config){
$this-&gt;code = 200;
return "Version 1.0";
}

}</pre>
<p>这里是一个可以使用的使用的API请求类型：</p>
<ul>
<li>Api_Request::METHOD_GET</li>
<li>Api_Request::METHOD_POST</li>
<li>Api_Request::METHOD_PUT</li>
<li>Api_Request::METHOD_DELETE</li>
</ul>
<p>如果你希望方法（ method）名以驼峰式命名的话，如：</p>
<p>public myNewHelloWorld{}</p>
<p>那么你就需要在你的url中使用”my-new-hello-world”来请求这个方法（/v1/helloworld/my-new-hello-world.xml）。</p>
<p><strong>Hook</strong></p>
<p>Hook是一个可以绑定特定行为的类。哪些Hooks 会在执行力中执行特定的点（如图所示）。它可以让你在服务使用前修改数据。下面是一些可能的hook示例：</p>
<ul>
<li>监测用户使用的API key是否在数据库中存在。</li>
<li>监测特定的IP地址是否是允许使用服务或行为的。</li>
<li>在文件或者数据库中记录用户的请求日志。</li>
<li>禁止特定的 IP使用API。</li>
<li>限制IP每小时对API的请求数。</li>
</ul>
<p>上面的只是一些示例，你可以发挥你的想象任意的添加Hook。下面是一个关于Hook的实例：</p>
<pre>class Api_Hook_BlockIp extends Api_Hook_IHook {

public function execute(){

// Current called service
$service = func_get_arg(0);

// Get config array
$config = $this-&gt;api-&gt;getConfig();

// Stop if blocks is not configured
if(!isset($config['block'])) return;

// Convert comma separated list to array
$blocked = explode(',', $config['block']);

// Check if the user IP is blocked
// If blocked show him a message
if(in_array($_SERVER['REMOTE_ADDR'], $blocked)){
    throw new Api_Error('Spammer', 'Your IP address is blocked.');
}
}

}</pre>
<p>为了使上面的Hook可以正常的工作，你需要在config文件中添加这样一行：</p>
<p>block = ip1,ip2,ip3</p>
<p>接下来就是讲解不同种类的hook了：</p>
<ul>
<li><strong>HOOK_CONFIG_LOADED:</strong> 这个hook是在加载了配置（config）请求的。他可以用来修改或添加一些配置信息</li>
<li><strong>HOOK_BEFORE_SERVICE_EXECUTE: </strong>这个hook是在服务执行前执行的，所以它可以用来校核用户身份，查看是否有具体的权限。</li>
<li><strong>HOOK_MODIFY_PARSER:</strong> 使用这个Hook可以在输出数据解析前修改解析器。</li>
</ul>
<p>如果要启用钩子则需要到程序的入口（endpoint.php）进行修改。</p>
<h4><strong>Parsers</strong></h4>
<p>解析器用来转化数据到特定的输出格式的。例如是XML，机械器就是用定义的APi请求文件扩展名来表示。例如：</p>
<ul>
<li><strong>/v1/helloworld.xml:</strong> 则使用Xml.php作为解析器</li>
<li><strong>/v1/helloworld.json:</strong> 则使用Json.php作为解析器</li>
</ul>
<p>你可以根据自己的需求定义标准的解析器，如：</p>
<ul>
<li>XML</li>
<li>CSV</li>
<li>JSON</li>
<li>Printr</li>
<li>TXT</li>
</ul>
<p>下面是一种类似PHP中print_r的输出格式示例：</p>
<pre>class Api_Parser_Printr extends Api_Parser_IParser{

/**
* Content type
* @var string
*/
public $content_type = "text/plain";

/**
* Parse to XML
*
* @return string
*/
public function parse(){
return print_r($this-&gt;_data, true);
}

}</pre>
<p>框架会根据命名规则自动寻找适合的输出格式。</p>
<p>要顺利的开发一个PHP API，除了以上的这些还需要做的有：</p>
<ol>
<li>创建一个基本的API配置文件，并修改好程序的入口。</li>
<li>配置自己想要的REST URL结构。</li>
<li>记录接口中产生的错误。~ </li>
</ol>
<p>Related posts:<ol>
<li><a href='http://www.biaodianfu.com/goo-gl-php-api.html' rel='bookmark' title='Goo.gl PHP API'>Goo.gl PHP API</a></li>
<li><a href='http://www.biaodianfu.com/php-curl-class.html' rel='bookmark' title='PHP 5 curl Class'>PHP 5 curl Class</a></li>
<li><a href='http://www.biaodianfu.com/php-google-urlshortener-api.html' rel='bookmark' title='最新Google短网址API（PHP）'>最新Google短网址API（PHP）</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/php-api-framework.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP Session学习笔记</title>
		<link>http://www.biaodianfu.com/learn-session.html</link>
		<comments>http://www.biaodianfu.com/learn-session.html#comments</comments>
		<pubDate>Mon, 12 Sep 2011 12:00:24 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[服务器]]></category>
		<category><![CDATA[session]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=4335</guid>
		<description><![CDATA[在web开发中，session是个非常重要的概念。Session一般译作会话，Session是一种基于HTTP协议的用以增强web应用能力的机制或者说一种方案，它不是单指某种特定的动态页面技术，而这种能力就是保持状态，也可以称作保持会话。 在许多动态网站的开发者看来，session就是一个变量，而且其表现像个黑洞，他只需要将东西在合适的时机放进这个洞里，等需要的时候再把东西取出来。这是开发者对session最直观的感受，但是黑洞里的景象或者说session内部到底是怎么工作的呢？ web应用是基于HTTP协议的，而HTTP协议是一种无状态协议。也就是说，用户从A页面跳转到B页面会重新发送一次HTTP请求，而服务端在返回响应的时候是无法获知该用户在请求B页面之前做了什么的。解决HTTP协议自身无状态的方式有cookie和session。二者都能记录状态，前者是将状态数据保存在客户端，后者则保存在服务端。 关于Cookie的介绍可以查看这两篇文章：Cookie简介或Cookie与Session的区别。今天主要讲的是Session的实现原理。 session的基本原理是服务端为每一个session维护一份会话信息数据，而客户端和服务端依靠一个全局唯一的标识来访问会话信息数据。用户访问web应用时，服务端程序决定何时创建session，创建session可以概括为三个步骤： 生成全局唯一标识符（sessionid） 开辟数据存储空间。一般会在内存中创建相应的数据结构，但这种情况下，系统一旦掉电，所有的会话数据就会丢失，如果是电子商务网站，这种事故会造成严重的后果。不过也可以写到文件里甚至存储在数据库中，这样虽然会增加I/O开销，但session可以实现某种程度的持久化，而且更有利于session的共享； 将session的全局唯一标示符发送给客户端。 关于服务器如何将session的唯一标识发送个客户端，主要有两种方式：cookie和URL重写。Cookie与Session的区别中也有写到，这里不再详述。下面就开始说说PHP中的Session。 PHP中session方案包含的信息 session id 用户session唯一标识符，随机生成的一串字符串，具有唯一性，随机性。主要用于区分其它用户的session数据。用户第一次访问web页面的时候，php的session初始化函数调用会分配给当前来访用户一个唯一的ID，也称之为session_id。 session data 我们把需要通过session保存的用户状态信息，称为用户session数据，也称为session数据。一般是在当前session生命周期，相应用的$_SESSION数据。 session file PHP默认将session数据存放在一个文件里。我们把存放session数据的文件称为session文件。它由特殊的php.ini设置session.save_path指定session文件的存放路径，CentOS5.3操作系统，PHP5.1默认存放在/var/lib/php/session目录中。用户session文件的名称，就是以sess_为前缀，以session_id为结尾命名，比如session id为vp8lfqnskjvsiilcp1c4l484d3，那么session文件名就是sess_vp8lfqnskjvsiilcp1c4l484d3 session lifetime 我们把初始化session开始，直到注销session这段期间，称为session生命周期，这样有助于我们理解session管理函数。 由此，我们可见：当每个用户访问web, PHP的session初始化函数都会给当前来访用户分配一个唯一的session ID。并且在session生命周期结束的时候，将用户在此周期产生的session数据持久到session文件中。用户再次访问的时候，session初始化函数，又会从session文件中读取session数据，开始新的session生命周期。 php.ini中与Session相关的设置： session.save_handler = file 用于读取/回写session数据的方式，默认是files。它会让PHP的session管理函数使用指定的文本文件存储session数据 session.save_path = “/var/lib/php/session” 指定保存session文件的目录，可以指定到别的目录，但是指定目录必须要有httpd守护进程属主(比如apache或www等)写权限，否则无法回存session数据。当指定目录不存在时，php session环境初始化函数是不会帮你创建指定目录的，所以需要你手工建立指定目录。它还可以写成这样session.save_path = “N;/path” 其中N是整数。这样使得不是所有的session文件都保存在同一个目录中，而是分散在不同目录。这对于服务器处理大量session文件是很有帮助的。（注:目录需要自己手工创建） session.auto_start = 0 如果启用该选项，用户的每次请求都会初始化session。我们推荐不启用该设置，最好通过session_start()显示地初始化session。 Session相关PHP函数和事件 session_start() 函数session_start会初始化session，也标识着session生命周期的开始。要使用session，必须初始化一个session环境。有点类似于OOP概念中调用构造函数构创建对象实例一样。session初始化操作，声明一个全局数组$_SESSION，映射寄存在内存的session数据。如果session文件已经存在，并且保存有session数据，session_start()则会读取session数据，填入$_SESSION中，开始一个新的session生命周期。 $_SESSION 它是一个全局变量，类型是Array，映射了session生命周期的session数据，寄存在内存中。在session初始化的时候，从session文件中读取数据，填入该变量中。在session生命周期结束时，将$_SESSION数据写回session文件。 session_register() 在session生命周期内，使用全局变量名称将注全局变量注册到当前session中。所谓注册，就是将变量填入$_SESSION中，值为NULL。它不会对session文件进行任何IO操作，只是影响$_SESSION变量。注意，它的正确写法是session_register(‘varname’)，而不是session_register($varname) session_unregister() 与session_register操作正好相反，即在session生命周期，从当前session注销指定变量。同样只影响$_SESSION，并不进行任何IO操作。 session_unset() 在session生命周期，从当前session中注销全部session数据，让$_SESSION成为一个空数组。它与unset($_SESSION)的区别在于:unset直接删除$_SESSION变量，释放内存资源;另一个区别在于，session_unset()仅在session生命周期能够操作$_SESSION数组，而unset()则在整个页面(page)生命周期都能操作$_SESSION数组。session_unset()同样不进行任何IO操作，只影响$_SESSION数组。 session_destroy() [...]]]></description>
			<content:encoded><![CDATA[<p>在web开发中，session是个非常重要的概念。Session一般译作会话，Session是一种基于HTTP协议的用以增强web应用能力的机制或者说一种方案，它不是单指某种特定的动态页面技术，而这种能力就是保持状态，也可以称作保持会话。</p>
<p>在许多动态网站的开发者看来，session就是一个变量，而且其表现像个黑洞，他只需要将东西在合适的时机放进这个洞里，等需要的时候再把东西取出来。这是开发者对session最直观的感受，但是黑洞里的景象或者说session内部到底是怎么工作的呢？</p>
<p>web应用是基于HTTP协议的，而HTTP协议是一种无状态协议。也就是说，用户从A页面跳转到B页面会重新发送一次HTTP请求，而服务端在返回响应的时候是无法获知该用户在请求B页面之前做了什么的。解决HTTP协议自身无状态的方式有cookie和session。二者都能记录状态，前者是将状态数据保存在客户端，后者则保存在服务端。</p>
<p>关于Cookie的介绍可以查看这两篇文章：<a href="http://www.biaodianfu.com/first-party-cookie-and-third-party-cookie.html">Cookie简介</a>或<a title="Cookie与Session的区别" href="http://www.biaodianfu.com/cookie-vs-session.html">Cookie与Session的区别</a>。今天主要讲的是Session的实现原理。</p>
<p>session的基本原理是服务端为每一个session维护一份会话信息数据，而客户端和服务端依靠一个全局唯一的标识来访问会话信息数据。用户访问web应用时，服务端程序决定何时创建session，创建session可以概括为三个步骤：</p>
<ol>
<li>生成全局唯一标识符（sessionid）</li>
<li>开辟数据存储空间。一般会在内存中创建相应的数据结构，但这种情况下，系统一旦掉电，所有的会话数据就会丢失，如果是电子商务网站，这种事故会造成严重的后果。不过也可以写到文件里甚至存储在数据库中，这样虽然会增加I/O开销，但session可以实现某种程度的持久化，而且更有利于session的共享；</li>
<li>将session的全局唯一标示符发送给客户端。</li>
</ol>
<p>关于服务器如何将session的唯一标识发送个客户端，主要有两种方式：cookie和URL重写。<a title="Cookie与Session的区别" href="http://www.biaodianfu.com/cookie-vs-session.html">Cookie与Session的区别</a>中也有写到，这里不再详述。下面就开始说说PHP中的Session。</p>
<p><strong>PHP中session方案包含的信息</strong></p>
<ol>
<li><strong>session id </strong>用户session唯一标识符，随机生成的一串字符串，具有唯一性，随机性。主要用于区分其它用户的session数据。用户第一次访问web页面的时候，php的session初始化函数调用会分配给当前来访用户一个唯一的ID，也称之为session_id。</li>
<li><strong>session data</strong> 我们把需要通过session保存的用户状态信息，称为用户session数据，也称为session数据。一般是在当前session生命周期，相应用的$_SESSION数据。</li>
<li><strong>session file</strong> PHP默认将session数据存放在一个文件里。我们把存放session数据的文件称为session文件。它由特殊的php.ini设置session.save_path指定session文件的存放路径，CentOS5.3操作系统，PHP5.1默认存放在/var/lib/php/session目录中。用户session文件的名称，就是以sess_为前缀，以session_id为结尾命名，比如session id为vp8lfqnskjvsiilcp1c4l484d3，那么session文件名就是sess_vp8lfqnskjvsiilcp1c4l484d3</li>
<li><strong>session lifetime</strong> 我们把初始化session开始，直到注销session这段期间，称为session生命周期，这样有助于我们理解session管理函数。</li>
</ol>
<p>由此，我们可见：当每个用户访问web, PHP的session初始化函数都会给当前来访用户分配一个唯一的session ID。并且在session生命周期结束的时候，将用户在此周期产生的session数据持久到session文件中。用户再次访问的时候，session初始化函数，又会从session文件中读取session数据，开始新的session生命周期。</p>
<p><strong>php.ini中与Session相关的设置：</strong></p>
<ol>
<li><em>session.save_handler = file</em> 用于读取/回写session数据的方式，默认是files。它会让PHP的session管理函数使用指定的文本文件存储session数据</li>
<li><em>session.save_path = “/var/lib/php/session”</em> 指定保存session文件的目录，可以指定到别的目录，但是指定目录必须要有httpd守护进程属主(比如apache或www等)写权限，否则无法回存session数据。当指定目录不存在时，php session环境初始化函数是不会帮你创建指定目录的，所以需要你手工建立指定目录。它还可以写成这样session.save_path = “N;/path” 其中N是整数。这样使得不是所有的session文件都保存在同一个目录中，而是分散在不同目录。这对于服务器处理大量session文件是很有帮助的。（注:目录需要自己手工创建）</li>
<li><em>session.auto_start = 0</em> 如果启用该选项，用户的每次请求都会初始化session。我们推荐不启用该设置，最好通过session_start()显示地初始化session。</li>
</ol>
<p><strong>Session相关PHP函数和事件</strong></p>
<ol>
<li><em>session_start()</em> 函数session_start会初始化session，也标识着session生命周期的开始。要使用session，必须初始化一个session环境。有点类似于OOP概念中调用构造函数构创建对象实例一样。session初始化操作，声明一个全局数组$_SESSION，映射寄存在内存的session数据。如果session文件已经存在，并且保存有session数据，session_start()则会读取session数据，填入$_SESSION中，开始一个新的session生命周期。</li>
<li><em>$_SESSION</em> 它是一个全局变量，类型是Array，映射了session生命周期的session数据，寄存在内存中。在session初始化的时候，从session文件中读取数据，填入该变量中。在session生命周期结束时，将$_SESSION数据写回session文件。</li>
<li><em>session_register()</em> 在session生命周期内，使用全局变量名称将注全局变量注册到当前session中。所谓注册，就是将变量填入$_SESSION中，值为NULL。它不会对session文件进行任何IO操作，只是影响$_SESSION变量。注意，它的正确写法是session_register(‘varname’)，而不是session_register($varname)</li>
<li><em>session_unregister()</em> 与session_register操作正好相反，即在session生命周期，从当前session注销指定变量。同样只影响$_SESSION，并不进行任何IO操作。</li>
<li><em>session_unset()</em> 在session生命周期，从当前session中注销全部session数据，让$_SESSION成为一个空数组。它与unset($_SESSION)的区别在于:unset直接删除$_SESSION变量，释放内存资源;另一个区别在于，session_unset()仅在session生命周期能够操作$_SESSION数组，而unset()则在整个页面(page)生命周期都能操作$_SESSION数组。session_unset()同样不进行任何IO操作，只影响$_SESSION数组。</li>
<li><em>session_destroy()</em> 如果说session_start()初始化一个session的话，而它则注销一个session。意味着session生命周期结束了。在session生命周期结整后，session_register, session_unset, session_register都将不能操作$_SESSION数组，而$_SESSION数组依然可以被unset()等函数操作。这时，session意味着是未定义的，而$_SESSION依然是一个全局变量，他们脱离了关映射关系。<br />
通过session_destroy()注销session,除了结束session生命周期外，它还会删除sesion文件，但不会影响当前$_SESSION变量。即它会产生一个IO操作。</li>
<li><em>session_regenerate_id()</em> 调用它，会给当前用户重新分配一个新的session id。并且在结束当前页面生命周期的时候，将当前session数据写入session文件。前提是，调用此函数之前，当前session生命周期没有被终止（参考第9点）。它会产生一个IO操作，创建一个新的session文件，创建新的session文件的是在session结束之前，而不是调用此函数就立即创建新的session文件。</li>
<li><em>session_commit()</em> session_commit()函数是session_write_close()函数的别名。它会结束当前session的生命周期，并且将session数据立即强制写入session文件。不推荐通过session_commit()来手工写入session数据，因为PHP会在页面生命周期结束的时候，自动结束当前没有终止的session生命周期。它会产生一个IO写操作。</li>
<li><em>end session</em> 结束session，默认是在页面生命周期结束的之前，PHP会自动结束当前没有终止的session。但是还可以通过session_commit()与session_destroy()二个函数提前结束session。不管是哪种方式，结束session都会产生IO操作，分别不一样。默认情况，产生一个IO写操作，将当前session数据写回session文件。session_commit()则是调用该函数那刻，产生一个IO写操作，将session数据写回session文件。而session_destroy()不一样在于，它不会将数据写回session文件，而是直接删除当前session文件。有趣的是，不管是session_commit()，还是session_destroy()都不会清空$_SESSION数组，更不会删除$_SESSION数组，只是所有session_*函数不能再操作session数据，因为当前的session生命周期终止了，即不能操作一个未定义对象。</li>
</ol>
<p><strong>Session ID 是如何传递的？</strong></p>
<p>session终究是因为管理用户状态信息才存在的。session id是用户表明身份的一种标识，就像入场券一样。用户一旦从被分配了session id之后的每次访问（http请求）都会携带这个session id给服务端，用于加载该用户的session数据。</p>
<p>用户端与服务端的web通信协议是http。而PHP通过http取得用户数据惯用的三种方法分别是:POST方法、GET方法还有Cookie。而PHP默认传递方法正是Cookie，也是最佳方法。只有在客户端不支持Cookie的时候（浏览器禁用了Cookie功能）才会通过GET方法来传递session_id，即通过在URL的query_string部分传递session id。</p>
<p>确定了传递方法，我们还有必要清楚一下session id的传递过程。用户通过浏览器访问网页，将URL输入地址栏回车，浏览器发出请求，在调用sockect send之前浏览器引擎会搜索有效的Cookies记录封装在http请求头的Cookie字段一同发送出去。服务端器接收到请求后，交给PHP处理。这时session初始化函数如果在$_COOKIE中没有找到以session_name()作为键值存储的生素（值为session id），则会以为用户是第一次访问web。作为第一次访问的用户，session初始化函数总会随机生成一个session_id并且通过setcookie()函数调用将新生成的session_id以”sesseson_name = session_id”的格式填入http响应头Set-Cookie字段，发送给客户端（这样接下来的请求，http请求头Cookie字段都会携带该Cookie记录给web服务器）。如果初始化函数发现用户端Cookies中已定义了存在$_COOKIE[‘sess_name’]，则会加载与$_COOKIE[‘sess_name’]相对应的session文件($_COOKIE[‘sess_name’]就是session ID)。如果用户Cookie记录过期，则会被浏览器删除。之后的下一次请求，服务器会以为用户又是第一次访问，如此循环。</p>
<p><strong>php.ini中Session ID 相关设置</strong></p>
<ol>
<li><em>session.use_cookie = 1</em> 是否采用Cookie方法传递session id值。默认是1，表示启用。</li>
<li><em>session.name = PHPSESSID</em> 不管是Cookie传递sessioin_id，还是GET方法传递session_id，都需要使用键值。他们的格式分别是Cookie:  sess_name=session_id;和/path.php?sess_name=session_id，其中sess_name就是由这里指定的。</li>
<li><em>session.use_only_cookies = 0</em> 表示只使用Cookie 的方法传递session id。我们说过，传递cookie的方法，除了cookie，还有GET方法，GET方法是不安全的方法。在用户端禁用了cookie的时候，会采用GET方法传递session_id，可以通过这个设置禁用GET方法传递session_id。</li>
<li><em>session.cookie_lifetime = 0, session.cookie_path = / 以及session.cookie_domain =</em> 如果使用Cookie方法传递session_id的话，这里分别指定了cookie有效域、目录和时间。分别对应setcookie()函数的形参$expire、$path和$domain。其中cookie_lifetime=0表示直到关闭浏览器才删除Cookie。还可以使用session_set_cookie_params()函数修改这些值。</li>
<li><em>session_name([string $name])</em> 获取或更新session_name。如果传了name，则表示不使用默认的名称PHPSESSID(由session.name)指定，否则获取当前session_name。注意:如果设置session_name，则必须在session_start()之前调用才生效。</li>
<li><em>session_id([string $id]) </em>与session_name()类似，但它是读取或者设置session_id的方法。同样，设置session_id的话，必须在session_start()之前调用才有效。</li>
<li><em>session_set_cookie_params()和session_get_cookie_params()</em> 通过session_set_cookie_params()可以重新设定session.cookie_lifetime, session.cookie_path以及session.cookie_domain这三个php.ini设置。而session_get_cookie_params()则是获取这些设定的值。</li>
</ol>
<p><strong>Session的回收</strong></p>
<p>我们知道session数据存放在服务端指定的session.save_path目录中，同时会在用户端存放一条Cookie用以记录分配给用户的session id。所以，session数据失效分服务端和客户端，要删除(回收)的对象也很清楚：</p>
<ol>
<li>服务端：删除过期的session文件，启动PHP GC回收。</li>
<li>用户端：使存储了过期session_id的用户端Cookie记录过期。通过将Cookie的Expire设置为负值，要求客户端删除Cookie。</li>
</ol>
<p><strong>服务端:删除过期的session文件</strong></p>
<p>PHP GC进程被启动以后，则会扫描session.save_path，找出过期的session，并删除该session文件。所谓，过期的session，是指操作系统当前时间与session文件最后访问时间之差大于session.gc_maxlifetime的话，该session认为是过期了。注意:有时候，你会发现，即便是文件过期了，有可能也没有被及时地删除掉。这是因为，每次session初始化的时候，并不会都启动PHP GC进程的，启动GC进程会大大降低php的运行效率。所有一个启动概率，这个概率由php.ini设定session.gc_probability / session.gc_divisor二个设置决定，默认概率是1%(1/1000)。这意味着，每1000次用户请求中,会启动1次PHP GC回收session文件。</p>
<p><strong>客户端:删除过期session id的cookie记录</strong></p>
<p>如果用户发现session已经过期，但是服务端的GC还没有启动，服务端可以手通过手工代码setcookie的方式要求用户端浏览器删除键值为session_name()的Cookie记录。这样，下回访问的时候，浏览器以为用户是第一次访问，并且重新给访问用户分配一个新的session_id。</p>
<p><strong>php.ini中与session相关的设置</strong></p>
<ol>
<li><em>session.gc_probability和session.gc_divisor</em> 由这二个函数决定了启用GC的概率,默认是1/1000。也就是说每一千次用户请求中有一次会启动GC回收session。启动GC进程不宜过于频繁。上面的例子我们可以看到它会每次检查session.save_path目录下每个文件的状态。这样会降低php的执行效率。</li>
<li><em>session.gc_maxlifetime = 1440 </em>设置session存活时间，单位是秒。每次GC启动后, 会通过stat得到session文件最后访问的unix时间,通过现在时间减去文件最后访问时间之间大于session.gc_maxlifetime则会删除该文件。</li>
</ol>
<p>参考资料：<a href="http://www.perfgeeks.com/?p=183">PHP5 Session解析I</a> 和 <a href="http://www.perfgeeks.com/?p=232">PHP5 Session解析II </a></p>
<p>Related posts:<ol>
<li><a href='http://www.biaodianfu.com/session-and-cookie.html' rel='bookmark' title='session和cookie的区别'>session和cookie的区别</a></li>
<li><a href='http://www.biaodianfu.com/gapi-google-analytics-php-interface.html' rel='bookmark' title='使用 PHP导出Google Analytics数据。'>使用 PHP导出Google Analytics数据。</a></li>
<li><a href='http://www.biaodianfu.com/first-party-cookie-and-third-party-cookie.html' rel='bookmark' title='网站统计：第一方Cookie和第三方Cookie'>网站统计：第一方Cookie和第三方Cookie</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/learn-session.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>获取客户端真实IP方法</title>
		<link>http://www.biaodianfu.com/ip-address.html</link>
		<comments>http://www.biaodianfu.com/ip-address.html#comments</comments>
		<pubDate>Sat, 04 Jun 2011 05:26:16 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[IP地址]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=3817</guid>
		<description><![CDATA[在讨论获取客户端IP 地址前，我们首先下弄明白的是以下三个的具体含义：REMOTE_ADDR，HTTP_CLIENT_IP，HTTP_X_FORWARDED_FOR REMOTE_ADDR 是你的客户端跟你的服务器“握手”时候的IP。如果使用了“匿名代理”，REMOTE_ADDR将显示代理服务器的IP。 HTTP_CLIENT_IP 是代理服务器发送的HTTP头。如果是“超级匿名代理”，则返回none值。同样，REMOTE_ADDR也会被替换为这个代理服务器的IP。 $_SERVER['REMOTE_ADDR']; //访问端（有可能是用户，有可能是代理的）IP $_SERVER['HTTP_CLIENT_IP'];   //代理端的（有可能存在，可伪造） $_SERVER['HTTP_X_FORWARDED_FOR']; //用户是在哪个IP使用的代理（有可能存在，也可以伪造） 一、没有使用代理服务器的情况： REMOTE_ADDR = 您的 IP HTTP_CLIENT_IP = 没数值或不显示 HTTP_X_FORWARDED_FOR = 没数值或不显示 二、使用透明代理服务器的情况：Transparent Proxies REMOTE_ADDR = 最后一个代理服务器 IP HTTP_CLIENT_IP = 代理服务器 IP HTTP_X_FORWARDED_FOR = 您的真实 IP ，经过多个代理服务器时，这个值类似如下：203.98.182.163, 203.98.182.163, 203.129.72.215。 这类代理服务器还是将您的信息转发给您的访问对象，无法达到隐藏真实身份的目的。 三、使用普通匿名代理服务器的情况：Anonymous Proxies REMOTE_ADDR = 最后一个代理服务器 IP HTTP_CLIENT_IP = 代理服务器 IP HTTP_X_FORWARDED_FOR = 代理服务器 IP ，经过多个代理服务器时，这个值类似如下：203.98.182.163, [...]]]></description>
			<content:encoded><![CDATA[<p>在讨论获取客户端IP 地址前，我们首先下弄明白的是以下三个的具体含义：REMOTE_ADDR，HTTP_CLIENT_IP，HTTP_X_FORWARDED_FOR</p>
<p>REMOTE_ADDR 是你的客户端跟你的服务器“握手”时候的IP。如果使用了“匿名代理”，REMOTE_ADDR将显示代理服务器的IP。 HTTP_CLIENT_IP 是代理服务器发送的HTTP头。如果是“超级匿名代理”，则返回none值。同样，REMOTE_ADDR也会被替换为这个代理服务器的IP。</p>
<ul>
<li>$_SERVER['REMOTE_ADDR']; //访问端（有可能是用户，有可能是代理的）IP</li>
<li>$_SERVER['HTTP_CLIENT_IP'];   //代理端的（有可能存在，可伪造）</li>
<li>$_SERVER['HTTP_X_FORWARDED_FOR']; //用户是在哪个IP使用的代理（有可能存在，也可以伪造）</li>
</ul>
<p>一、没有使用代理服务器的情况：</p>
<ul>
<li>REMOTE_ADDR = 您的 IP</li>
<li>HTTP_CLIENT_IP = 没数值或不显示</li>
<li>HTTP_X_FORWARDED_FOR = 没数值或不显示</li>
</ul>
<p>二、使用透明代理服务器的情况：Transparent Proxies</p>
<ul>
<li>REMOTE_ADDR = 最后一个代理服务器 IP</li>
<li>HTTP_CLIENT_IP = 代理服务器 IP</li>
<li>HTTP_X_FORWARDED_FOR = 您的真实 IP ，经过多个代理服务器时，这个值类似如下：203.98.182.163, 203.98.182.163, 203.129.72.215。</li>
</ul>
<p>这类代理服务器还是将您的信息转发给您的访问对象，无法达到隐藏真实身份的目的。</p>
<p>三、使用普通匿名代理服务器的情况：Anonymous Proxies</p>
<ul>
<li>REMOTE_ADDR = 最后一个代理服务器 IP</li>
<li>HTTP_CLIENT_IP = 代理服务器 IP</li>
<li>HTTP_X_FORWARDED_FOR = 代理服务器 IP ，经过多个代理服务器时，这个值类似如下：203.98.182.163, 203.98.182.163, 203.129.72.215。</li>
</ul>
<p>隐藏了您的真实IP，但是向访问对象透露了您是使用代理服务器访问他们的。</p>
<p>四、使用欺骗性代理服务器的情况：Distorting Proxies</p>
<ul>
<li>REMOTE_ADDR = 代理服务器 IP</li>
<li>HTTP_CLIENT_IP = 代理服务器 IP</li>
<li>HTTP_X_FORWARDED_FOR = 随机的 IP ，经过多个代理服务器时，这个值类似如下：203.98.182.163, 203.98.182.163, 203.129.72.215。</li>
</ul>
<p>告诉了访问对象您使用了代理服务器，但编造了一个虚假的随机IP代替您的真实IP欺骗它。</p>
<p>五、使用高匿名代理服务器的情况：High Anonymity Proxies (Elite proxies)</p>
<ul>
<li>REMOTE_ADDR = 代理服务器 IP</li>
<li>HTTP_CLIENT_IP = 没数值或不显示</li>
<li>HTTP_X_FORWARDED_FOR = 没数值或不显示 ，经过多个代理服务器时，这个值类似如下：203.98.182.163, 203.98.182.163, 203.129.72.215。</li>
</ul>
<p>完全用代理服务器的信息替代了您的所有信息，就象您就是完全使用那台代理服务器直接访问对象。</p>
<p>了解了以上那么多的信息，下面就来我们一起学习下如何获取客户端的真实IP地址，以下分别为PHPWind和Discuz的获取IP真实的方法：</p>
<p>phpwind：</p>
<pre lang="php" escaped="true">if($_SERVER['HTTP_X_FORWARDED_FOR']){
 $onlineip = $_SERVER['HTTP_X_FORWARDED_FOR'];
 $c_agentip=1;
} elseif($_SERVER['HTTP_CLIENT_IP']){
 $onlineip = $_SERVER['HTTP_CLIENT_IP'];
 $c_agentip=1;
} else{
 $onlineip = $_SERVER['REMOTE_ADDR'];
 $c_agentip=0;
}</pre>
<p>disuz：</p>
<pre lang="php" escaped="true">if(getenv('HTTP_CLIENT_IP') &amp;&amp; strcasecmp(getenv('HTTP_CLIENT_IP'), 'unknown')) {
 $onlineip = getenv('HTTP_CLIENT_IP');
} elseif(getenv('HTTP_X_FORWARDED_FOR') &amp;&amp; strcasecmp(getenv('HTTP_X_FORWARDED_FOR'), 'unknown')) {
 $onlineip = getenv('HTTP_X_FORWARDED_FOR');
} elseif(getenv('REMOTE_ADDR') &amp;&amp; strcasecmp(getenv('REMOTE_ADDR'), 'unknown')) {
 $onlineip = getenv('REMOTE_ADDR');
} elseif(isset($_SERVER['REMOTE_ADDR']) &amp;&amp; $_SERVER['REMOTE_ADDR'] &amp;&amp; strcasecmp($_SERVER['REMOTE_ADDR'], 'unknown')) {
 $onlineip = $_SERVER['REMOTE_ADDR'];
}</pre>
<p>其中PHPWind使用了$_SERVER，而Discuz使用了getenv，我们就来说说两者的区别。</p>
<p>getenv默认不支持IIS的isapi方式运行的php，在IIS下的PHP环境里，php.ini 中的 variables_order = “EGPCS” 当包括 “E” 时getenv才起作用，否则是取不到任何数据的。</p>
<p>资料来源于网络，没有亲自测试，可能有误~</p>
<p>Related posts:<ol>
<li><a href='http://www.biaodianfu.com/qq-ip-addres.html' rel='bookmark' title='腾讯IP地址API的使用'>腾讯IP地址API的使用</a></li>
<li><a href='http://www.biaodianfu.com/googlebot-ua-ip.html' rel='bookmark' title='Google蜘蛛UA及IP'>Google蜘蛛UA及IP</a></li>
<li><a href='http://www.biaodianfu.com/scraping-google-serp.html' rel='bookmark' title='怎样抓取Google的搜索结果页'>怎样抓取Google的搜索结果页</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/ip-address.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP中文高速分词的原理和源码</title>
		<link>http://www.biaodianfu.com/php-fenci.html</link>
		<comments>http://www.biaodianfu.com/php-fenci.html#comments</comments>
		<pubDate>Sat, 26 Feb 2011 03:26:06 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[搜索优化]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[网络营销]]></category>
		<category><![CDATA[分词]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=3368</guid>
		<description><![CDATA[一、正向最大匹配算法和反向最大匹配算法的缺点 正向最大匹配算法：从左到右将待分词文本中的几个连续字符与词表匹配，如果匹配上，则切分出一个词。但这里有一个问题：要做到最大匹配，并不是第一次匹配到就可以切分的。举个例子：中华人民共和国今天成立了。从左到右扫描，要分别检索：中，中华，中华人，中华人民，中华人民共，中华人民共和，中华人民共和国今，今，今天，今天成，成，成立，成立了，了。14 次检索词库，最后的切分结果：中华人民共和国/今天/成立了。所以，当遇到长词时，要反复检索多次数据库，效率非常差。还有，一个更严重的问题是：词的最大长度是有限制的，为了兼顾算法的效率，不可能将最大词长定的非常大，这就会导致更长的词汇不能正确切分。 反之，反向最大匹配算法，则会将长词分开，造成错误切分。比如，上面的待切分文本，从右向左扫描，要分别检索：了，立了，立，成立，天成立，天，今天，今天国，国，和国，共和国，民共和国，民，人民，华人民，华，中华。17 词查询数据库，最后切分结果：中华/人民/共和国/今天/成立/了。将中华人民共和国切分成了3 个词。 二、克服最大匹配算法的缺点的算法 为了克服最大匹配算法的低效和不能切分长词，将所有的能组成词汇的汉字，建立索引，作为词的首字母。然后将每个汉字开头的词汇，分成一类，按词长排序。词库结构如下： 分词时，由汉字找到该字开头的词组（长度3000左右的线性检索），然后按由长到短5，4，3，2的顺序检索词库，和待分词语句对比（线性），如果有匹配，则切分为一个词，然后继续匹配下一个词。通过这种方式，大大提高了检索词库效率，解决了任意长词汇匹配问题。 在PHP算法的实现上，为了加快在线匹配速度，上面的词库结构，用PHP的联想数组的形式实现，全部加载到内存。为了灵活增删词库，做了个字符串处理程序，自动生成PHP联想数组结构的词库。详细实现算法，见PHP源码。 PHP分词源码下载：http://www.box.net/shared/gryspzppsb 原文出处：http://bbs.goocnjp.com/thread-385.html No related posts.]]></description>
			<content:encoded><![CDATA[<p><strong>一、正向最大匹配算法和反向最大匹配算法的缺点</strong></p>
<p>正向最大匹配算法：从左到右将待分词文本中的几个连续字符与词表匹配，如果匹配上，则切分出一个词。但这里有一个问题：要做到最大匹配，并不是第一次匹配到就可以切分的。举个例子：<em>中华人民共和国今天成立了</em>。从左到右扫描，要分别检索：中，中华，中华人，中华人民，中华人民共，中华人民共和，中华人民共和国今，今，今天，今天成，成，成立，成立了，了。14 次检索词库，最后的切分结果：中华人民共和国/今天/成立了。所以，当遇到长词时，要反复检索多次数据库，效率非常差。还有，一个更严重的问题是：词的最大长度是有限制的，为了兼顾算法的效率，不可能将最大词长定的非常大，这就会导致更长的词汇不能正确切分。</p>
<p>反之，反向最大匹配算法，则会将长词分开，造成错误切分。比如，上面的待切分文本，从右向左扫描，要分别检索：了，立了，立，成立，天成立，天，今天，今天国，国，和国，共和国，民共和国，民，人民，华人民，华，中华。17 词查询数据库，最后切分结果：中华/人民/共和国/今天/成立/了。将中华人民共和国切分成了3 个词。</p>
<p><strong>二、克服最大匹配算法的缺点的算法</strong></p>
<p>为了克服最大匹配算法的低效和不能切分长词，将所有的能组成词汇的汉字，建立索引，作为词的首字母。然后将每个汉字开头的词汇，分成一类，按词长排序。词库结构如下：</p>
<p><img class="alignnone size-full wp-image-3369" title="fenci" src="http://www.biaodianfu.com/wp-content/uploads/2011/02/fenci1.png" alt="" width="604" height="283" /></p>
<p>分词时，由汉字找到该字开头的词组（长度3000左右的线性检索），然后按由长到短5，4，3，2的顺序检索词库，和待分词语句对比（线性），如果有匹配，则切分为一个词，然后继续匹配下一个词。通过这种方式，大大提高了检索词库效率，解决了任意长词汇匹配问题。</p>
<p>在PHP算法的实现上，为了加快在线匹配速度，上面的词库结构，用PHP的联想数组的形式实现，全部加载到内存。为了灵活增删词库，做了个字符串处理程序，自动生成PHP联想数组结构的词库。详细实现算法，见PHP源码。</p>
<p>PHP分词源码下载：<a href="http://www.box.net/shared/gryspzppsb">http://www.box.net/shared/gryspzppsb</a></p>
<p>原文出处：<a href="http://bbs.goocnjp.com/thread-385.html">http://bbs.goocnjp.com/thread-385.html</a></p>
<p>No related posts.</p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/php-fenci.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>使用PHP对网站验证码进行破解</title>
		<link>http://www.biaodianfu.com/php-crack-captcha.html</link>
		<comments>http://www.biaodianfu.com/php-crack-captcha.html#comments</comments>
		<pubDate>Wed, 09 Feb 2011 13:08:52 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[验证码]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=3264</guid>
		<description><![CDATA[验证码的功能一般是防止使用程序恶意注册、暴力破解或批量发帖而设置的。所谓验证码，就是将一串随机产生的数字或符号，生成一幅图片，图片里加上一些干扰象素（防止OCR），由用户肉眼识别其中的验证码信息，输入表单提交网站验证，验证成功后才能使用某项功能。学习验证码的破解/识别技术，不仅可以知道验证码的原理，而且可以让你知道怎样才能防止验证码被破解。 最常见的验证码主要有以下几种： 四位数字，随机的一数字字符串，最原始的验证码，验证作用几乎为零。 随机数字图片验证码。图片上的字符比较中规中矩，有的可能加入一些随机干扰素，还有一些是随机字符颜色，验证作用比上一个好。没有基本图形图像学知识的人，不可破！ 各种图片格式的随机数字+随机大写英文字母+随机干扰像素+随机位置。 汉字是注册目前最新的验证码，随机生成，打起来更难了,影响用户体验，所以，一般应用的比较少。 为简单起见，破解说明主要针对是第2种类型的，先来看看网上常见的这种验证码的图片： 第一种，最容易，图片背景和数字都使用相同的颜色，字符规整，字符位置统一。 第二种，看似不容易，其实仔细研究会发现其规则，背景色和干扰素无论怎么变化，验证字符字符规整，颜色相同，所以排除干扰素非常容易，只要是非字符色素全部排除即可。 第三种，看似更复杂，处理上面提到背景色和干扰素一直变化外，验证字符的颜色也在变化，并且各个字符的颜色也各不相同。 第四种，除了第三个图片上提到的特征外，又在文字上加了两条直线干扰率，看似困难其实，很容易去掉。 验证码识别一般分为以下几个步骤： 取出字模 识别验证码，毕竟不是专业的OCR识别，并且，由于各个网站的验证码各不相同，所以，最常见的方法就是就是建立这个验证码的特征码库。去字模时，我们需要多下载几张图片，使这些图片中，包括所有的字符，我们这里的字母只有图片，所以，只要收集到包括0-9的图片即可。 二值化 二值化就是把图片上的验证数字上每个象素用一种数字表示1，其他部分用0表示。这样就可以计算出每个数字字模，记录下这些字模来，当作key即可。 计算特征 把要识别的图片，进行二值化，得到图片特征。 对照样本 把步骤3种的图片特征码和验证码的字模进行对比，得到验证图片上的数字。 使用目前这种方法，对验证码的识别基本上可以做到100%。 通过以上步骤，您可能说了，并没有发现如何取出干扰素啊！其实取出干扰素的方法很简单，干扰素的一个重要特征是，不能影响验证码的显示效果，所以制作干扰素时它的RGB可能低于或者高于某个特定值，比如我给的例子中的图片，干扰素的RGB各项值是不会超过125的，所以，这样我们就很容易去掉干扰素了。 简单的验证码只有数字和字母组成，格式统一，每次出现位置固定。下面继续深入研究识别验证码，这次需要识别的目标是：验证码有字符和数字组成，验证码存在旋转（可能左右都旋转），位置不固定，存在字符与字符之间的粘连，且验证码有更强的干扰素。 我们以下图为例进行讲解。 第一步：二值化。把验证码的部分用 1 表示，背景部分用 0 表示出来，识别方法很简单，我们打印出验证码整张图片的 RGB ，然后分析其规律即可，通过 RGB 码，我们很容易分辨出上面这张图片的 R 值大于 120 ， G 和 B 的值小于 80 ，所以依据这个规则我们很容易把上面的图片二值化。 再来看看上面的第三种验证码图片 刚看上去，感觉很复杂。验证码的图片每次背景色都不相同，且不是单色，各个验证码数字的颜色每次也各不相同。貌似很难二值化，其实我们打印出其 RGB 值很容易就发现。无论验证数字颜色如何变化，该数字的 RGB 值总有一个值小于 125 ，所以通过如下判断 $rgbarray['red'] &#60; 125 &#124;&#124; $rgbarray['green']&#60;125&#124;&#124; $rgbarray['blue'] &#60; 125 我们就很容易分辨出哪里是数字，哪里是背景。 我们能够找到这些规律的原因是，在制作验证码的干扰素时，为了使干扰素不影响数字的显示效果，必须使用干扰素的 RGB 和数字 RGB 相互独立，互不干扰。只要懂得这个规律，我们就很容易实现二值化。 我们找到的 120 ， 80 ， 125 等阈值，可能和实际的 RGB 有出入，所以，有时二值化后，会有部分地方出现 1 ，对于验证码上固定位置显示数字，这种干扰没有太大意义。但是对于验证码位置不确定的图片来说，在我们切割字符时，很可能造成干扰。所以，在二值化后要进行去噪处理。 第二部：去噪处理。去噪的原理很简单，就是把孤立的有效的值去掉，如果噪点比较高，要求的效率也比较高的话，这里面也有很多工作要做。幸好这里我们不要求这么高深，我们使用最简单的方法就可以，如果一个点为 1 则判断这个点的上下左右上左上右下左下右 8 个方位上数字是否为 1 ，如果不为 1 ，就认为是一个燥点，直接设置为 1 即可。 如上图所示，我们使用此方法很容易发现红色方框部分的 1 为燥点，直接设置为 1 即可。在判断时我们使用了一个技巧，有时候的噪点可能是两个连续的 1 ，所以我们计算这个点的 8 个方向上的值之和，最后我们判断他们的和是否小于特定的阈值。 第三部：切割字符。切割字符的方法有很多种，这里采用最简单的一种，先垂直方向切割成为字符，然后在水平方向去掉多于的 0000 ，如下图 第一步切割红线部分，第二步切割蓝线部分，这样就可以得到独立的字符了。但是像下面这种情况 按上面的方法会把 dw 字符切割成一个字符，这是错误的切割，所以这里我们涉及到粘连字符的切割。 第四步：粘连字符切割。制作验证码时，规则字符的粘连很容易分割开，如果字符本身有缩放，变形就很难处理，经过分析，我们可以发现，上面的字符粘连属于很简单的方式，只是规则字符的粘连，所以处理这种情况，我们也使用很简单的处理方式。当完成分割操作后，我们不能马上确定分割的部分就为一个字符，要进行验证，验证的关键因素就是，切割下来的字符的宽是否大于阈值，这个阈值的取舍标准是，一个字符无论怎么旋转变形都不会大于这个阈值，所以，如果我们切割的块大于这个阈值，就可以认为这是一个粘连字符；如果大于两个阈值之和，就认为是三个字符粘连，以此类推。知道这个规则后，切割粘连字符也就很简单了。如果我们发现是粘连字符块，直接平分这个块为两个或者多个新的块就可以。当然为了更好的还原字符，我一般都采用平分 +1 ， -1 对字符块的部分进行适当的补充。 第五步：匹配字符。对于旋转字符的特征码建立，有很多种方法，这里就不做深入研究了。我这里使用的最简单的方式，为所有字符的所有情况建立匹配库，所以在我提供的代码种增加了 study 操作，其目的就是，先有人手工识别图片的验证码，然后通过 study 方法，写入特征码库。这样写入的图片数据越多，验证识别的准确行也就越高。 经过以上步骤，我们基本上可以识别现在互联网上大部分的验证码，这里我们都是使用的最简单的方法，没有使用任何 OCR 知识。 另外制作验证码的一些建议： 对于识别验证码的程序来说，最难得部分是验证字符的切割和特征码的建立，而国内很多程序员只做验证码时，总是喜欢在验证码加很多干扰素，干扰线，影响效果不说，还达不到很好的效果；所以，要想使自己验证码难于本识别，只做下面两点就够了 字符粘连，最好所有的字符都有粘连的部分； 不要使用规格字符，验证码的各个部分使用不同比例的缩放或者旋转。 只要做到这两点，或者这两点的变形，识别程序就很难识别。具体参考Google的验证码即可。 原文链接： php实现验证码的识别(初级篇) http://blog.csdn.net/ugg/archive/2009/03/03/3953137.aspx php实现验证码的识别(中级篇) http://blog.csdn.net/ugg/archive/2009/03/09/3972368.aspx 相关PHP程序下载：http://www.box.net/shared/9f4c23ysd8 [...]]]></description>
			<content:encoded><![CDATA[<p>验证码的功能一般是防止使用程序恶意注册、暴力破解或批量发帖而设置的。所谓验证码，就是将一串随机产生的数字或符号，生成一幅图片，图片里加上一些干扰象素（防止OCR），由用户肉眼识别其中的验证码信息，输入表单提交网站验证，验证成功后才能使用某项功能。学习验证码的破解/识别技术，不仅可以知道验证码的原理，而且可以让你知道怎样才能防止验证码被破解。</p>
<p>最常见的验证码主要有以下几种：</p>
<ol>
<li>四位数字，随机的一数字字符串，最原始的验证码，验证作用几乎为零。</li>
<li>随机数字图片验证码。图片上的字符比较中规中矩，有的可能加入一些随机干扰素，还有一些是随机字符颜色，验证作用比上一个好。没有基本图形图像学知识的人，不可破！</li>
<li>各种图片格式的随机数字+随机大写英文字母+随机干扰像素+随机位置。</li>
<li>汉字是注册目前最新的验证码，随机生成，打起来更难了,影响用户体验，所以，一般应用的比较少。</li>
</ol>
<p>为简单起见，破解说明主要针对是第2种类型的，先来看看网上常见的这种验证码的图片：</p>
<p><img class="alignnone size-full wp-image-3265" title="captcha" src="http://www.biaodianfu.com/wp-content/uploads/2011/02/captcha.png" alt="" width="412" height="94" /></p>
<ul>
<li>第一种，最容易，图片背景和数字都使用相同的颜色，字符规整，字符位置统一。</li>
<li>第二种，看似不容易，其实仔细研究会发现其规则，背景色和干扰素无论怎么变化，验证字符字符规整，颜色相同，所以排除干扰素非常容易，只要是非字符色素全部排除即可。</li>
<li>第三种，看似更复杂，处理上面提到背景色和干扰素一直变化外，验证字符的颜色也在变化，并且各个字符的颜色也各不相同。</li>
<li>第四种，除了第三个图片上提到的特征外，又在文字上加了两条直线干扰率，看似困难其实，很容易去掉。</li>
</ul>
<p>验证码识别一般分为以下几个步骤：</p>
<ol>
<li><strong>取出字模</strong> 识别验证码，毕竟不是专业的OCR识别，并且，由于各个网站的验证码各不相同，所以，最常见的方法就是就是建立这个验证码的特征码库。去字模时，我们需要多下载几张图片，使这些图片中，包括所有的字符，我们这里的字母只有图片，所以，只要收集到包括0-9的图片即可。</li>
<li><strong>二值化</strong> 二值化就是把图片上的验证数字上每个象素用一种数字表示1，其他部分用0表示。这样就可以计算出每个数字字模，记录下这些字模来，当作key即可。</li>
<li><strong>计算特征</strong> 把要识别的图片，进行二值化，得到图片特征。</li>
<li><strong>对照样本</strong> 把步骤3种的图片特征码和验证码的字模进行对比，得到验证图片上的数字。</li>
</ol>
<p>使用目前这种方法，对验证码的识别基本上可以做到100%。</p>
<p>通过以上步骤，您可能说了，并没有发现如何取出干扰素啊！其实取出干扰素的方法很简单，干扰素的一个重要特征是，不能影响验证码的显示效果，所以制作干扰素时它的RGB可能低于或者高于某个特定值，比如我给的例子中的图片，干扰素的RGB各项值是不会超过125的，所以，这样我们就很容易去掉干扰素了。</p>
<p>简单的验证码只有数字和字母组成，格式统一，每次出现位置固定。下面继续深入研究识别验证码，这次需要识别的目标是：验证码有字符和数字组成，验证码存在旋转（可能左右都旋转），位置不固定，存在字符与字符之间的粘连，且验证码有更强的干扰素。</p>
<p>我们以下图为例进行讲解。</p>
<p><img class="alignnone size-full wp-image-3268" title="captcha_crack" src="http://www.biaodianfu.com/wp-content/uploads/2011/02/captcha_crack.jpg" alt="" width="70" height="32" /></p>
<p><strong>第一步：二值化。</strong>把验证码的部分用 1 表示，背景部分用 0 表示出来，识别方法很简单，我们打印出验证码整张图片的 RGB ，然后分析其规律即可，通过 RGB 码，我们很容易分辨出上面这张图片的 R 值大于 120 ， G 和 B 的值小于 80 ，所以依据这个规则我们很容易把上面的图片二值化。</p>
<p>再来看看上面的第三种验证码图片</p>
<p><img class="alignnone size-full wp-image-3269" title="captcha_3" src="http://www.biaodianfu.com/wp-content/uploads/2011/02/captcha_3.jpg" alt="" width="60" height="20" /></p>
<p>刚看上去，感觉很复杂。验证码的图片每次背景色都不相同，且不是单色，各个验证码数字的颜色每次也各不相同。貌似很难二值化，其实我们打印出其 RGB 值很容易就发现。无论验证数字颜色如何变化，该数字的 RGB 值总有一个值小于 125 ，所以通过如下判断 <em>$rgbarray['red'] &lt; 125 || $rgbarray['green']&lt;125|| $rgbarray['blue'] &lt; 125</em> 我们就很容易分辨出哪里是数字，哪里是背景。</p>
<p>我们能够找到这些规律的原因是，在制作验证码的干扰素时，为了使干扰素不影响数字的显示效果，必须使用干扰素的 RGB 和数字 RGB 相互独立，互不干扰。只要懂得这个规律，我们就很容易实现二值化。</p>
<p>我们找到的 120 ， 80 ， 125 等阈值，可能和实际的 RGB 有出入，所以，有时二值化后，会有部分地方出现 1 ，对于验证码上固定位置显示数字，这种干扰没有太大意义。但是对于验证码位置不确定的图片来说，在我们切割字符时，很可能造成干扰。所以，在二值化后要进行去噪处理。</p>
<p><strong>第二部：去噪处理。</strong>去噪的原理很简单，就是把孤立的有效的值去掉，如果噪点比较高，要求的效率也比较高的话，这里面也有很多工作要做。幸好这里我们不要求这么高深，我们使用最简单的方法就可以，如果一个点为 1 则判断这个点的上下左右上左上右下左下右 8 个方位上数字是否为 1 ，如果不为 1 ，就认为是一个燥点，直接设置为 1 即可。</p>
<p><img class="alignnone size-full wp-image-3270" title="quzao" src="http://www.biaodianfu.com/wp-content/uploads/2011/02/quzao.jpg" alt="" width="560" height="325" /></p>
<p>如上图所示，我们使用此方法很容易发现红色方框部分的 1 为燥点，直接设置为 1 即可。在判断时我们使用了一个技巧，有时候的噪点可能是两个连续的 1 ，所以我们计算这个点的 8 个方向上的值之和，最后我们判断他们的和是否小于特定的阈值。</p>
<p><strong>第三部：切割字符</strong>。切割字符的方法有很多种，这里采用最简单的一种，先垂直方向切割成为字符，然后在水平方向去掉多于的 0000 ，如下图</p>
<p><img class="alignnone size-full wp-image-3271" title="qiegezifu" src="http://www.biaodianfu.com/wp-content/uploads/2011/02/qiegezifu.jpg" alt="" width="561" height="286" /></p>
<p>第一步切割红线部分，第二步切割蓝线部分，这样就可以得到独立的字符了。但是像下面这种情况</p>
<p><img class="alignnone size-full wp-image-3272" title="zifuqiege" src="http://www.biaodianfu.com/wp-content/uploads/2011/02/zifuqiege.jpg" alt="" width="569" height="293" /></p>
<p>按上面的方法会把 dw 字符切割成一个字符，这是错误的切割，所以这里我们涉及到粘连字符的切割。</p>
<p><strong>第四步：粘连字符切割。</strong>制作验证码时，规则字符的粘连很容易分割开，如果字符本身有缩放，变形就很难处理，经过分析，我们可以发现，上面的字符粘连属于很简单的方式，只是规则字符的粘连，所以处理这种情况，我们也使用很简单的处理方式。当完成分割操作后，我们不能马上确定分割的部分就为一个字符，要进行验证，验证的关键因素就是，切割下来的字符的宽是否大于阈值，这个阈值的取舍标准是，一个字符无论怎么旋转变形都不会大于这个阈值，所以，如果我们切割的块大于这个阈值，就可以认为这是一个粘连字符；如果大于两个阈值之和，就认为是三个字符粘连，以此类推。知道这个规则后，切割粘连字符也就很简单了。如果我们发现是粘连字符块，直接平分这个块为两个或者多个新的块就可以。当然为了更好的还原字符，我一般都采用平分 +1 ， -1 对字符块的部分进行适当的补充。</p>
<p><strong>第五步：匹配字符。</strong>对于旋转字符的特征码建立，有很多种方法，这里就不做深入研究了。我这里使用的最简单的方式，为所有字符的所有情况建立匹配库，所以在我提供的代码种增加了 study 操作，其目的就是，先有人手工识别图片的验证码，然后通过 study 方法，写入特征码库。这样写入的图片数据越多，验证识别的准确行也就越高。</p>
<p>经过以上步骤，我们基本上可以识别现在互联网上大部分的验证码，这里我们都是使用的最简单的方法，没有使用任何 OCR 知识。</p>
<p><strong>另外制作验证码的一些建议：</strong></p>
<p>对于识别验证码的程序来说，最难得部分是验证字符的切割和特征码的建立，而国内很多程序员只做验证码时，总是喜欢在验证码加很多干扰素，干扰线，影响效果不说，还达不到很好的效果；所以，要想使自己验证码难于本识别，只做下面两点就够了</p>
<ol>
<li>字符粘连，最好所有的字符都有粘连的部分；</li>
<li>不要使用规格字符，验证码的各个部分使用不同比例的缩放或者旋转。</li>
</ol>
<p>只要做到这两点，或者这两点的变形，识别程序就很难识别。具体参考Google的验证码即可。</p>
<p>原文链接：</p>
<p>php实现验证码的识别(初级篇) <a href="http://blog.csdn.net/ugg/archive/2009/03/03/3953137.aspx">http://blog.csdn.net/ugg/archive/2009/03/03/3953137.aspx</a></p>
<p>php实现验证码的识别(中级篇) <span style="font-weight: normal; font-size: 13px;"><a href="http://blog.csdn.net/ugg/archive/2009/03/09/3972368.aspx">http://blog.csdn.net/ugg/archive/2009/03/09/3972368.aspx</a></span></p>
<p><span style="font-weight: normal; font-size: 13px;">相关PHP程序下载：<a href="http://www.box.net/shared/9f4c23ysd8">http://www.box.net/shared/9f4c23ysd8</a></span></p>
<p>Related posts:<ol>
<li><a href='http://www.biaodianfu.com/dbexportdoc-for-mysql.html' rel='bookmark' title='MySQL数据库表结构导出工具'>MySQL数据库表结构导出工具</a></li>
<li><a href='http://www.biaodianfu.com/shenru-sousuoyinqing.html' rel='bookmark' title='《深入搜索引擎》PDF电子书下载'>《深入搜索引擎》PDF电子书下载</a></li>
<li><a href='http://www.biaodianfu.com/new-google-rankings-spam.html' rel='bookmark' title='一些最新的谷歌排名规则'>一些最新的谷歌排名规则</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/php-crack-captcha.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Google Chart API：二维码</title>
		<link>http://www.biaodianfu.com/google-chart-api-qrcode.html</link>
		<comments>http://www.biaodianfu.com/google-chart-api-qrcode.html#comments</comments>
		<pubDate>Wed, 09 Feb 2011 08:03:56 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[二维码]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=3259</guid>
		<description><![CDATA[QRcode是二维码的一种。QRcode可以存储最多4296个字母数字类型的任意文本。这些文本可以是任何内容，例如，网址、联系信息、电话号码（具体科查看二维码数据格式）。QR code存储的信息可以被安装有适当软件的光学设备读取。这种设备既可以是专用的QR code读取器也可以是手机。 通过调用 Google Chart Tools / Image Charts 的 API ，我们可以很方便的生成QRcode。 调用方式也很简单，只要向 http://chart.apis.google.com/chart 传入适合的参数就可以了，参数如下： cht=qr 这个是必需的，告诉 API ，你需要生成的是二维码。 chs=&#60;width&#62;x&#60;height&#62; 这个同样是必需的，告诉 API ，你需要生成的二维码的尺寸。 chl=&#60;data&#62; 这个还是必需的，用来告诉 API 二维码所包含的信息。可以是数字、字符数字、字符、二进制信息、汉字。不能混合数据类型。数据必须经过UTF-8 URL-encoded。如果需要传递的信息超过2K个字节，请使用POST方式。 choe=&#60;output_encoding&#62; 终于来了个不是必须的，这个是用来声明生成的二维码所包含信息的编码，默认是 UTF-8 ；其他可选编码是 Shift_JIS 、 ISO-8859-1 chld=&#60;error_correction_level&#62;&#124;&#60;margin&#62; 可选 纠错等级。QR码支持四个等级的纠错，用来恢复丢失的、读错的、模糊的、数据。下面是可选的值：L-(默认)可以识别已损失7%的数据；M-可以识别已损失15%的数据；Q-可以识别已损失25%的数据；H-可以识别已损失30%的数据。margin 是指生成的二维码离图片边框的距离。 QR码是方形的，有相同的长和宽。QR码的大小是固定的：从21到177的长/宽，每次递增4个像素点。每个配置被称为一个等级。长和宽越大，存储的信息就越多。下面是版本摘要: 等级为1的QR码长和宽分别为21个像素，最多可以存储25个字母数字和字符。 等级为2的QR码长和宽分别为25个像素，最多可以存储47个字母数字和字符。 …以此类推 。 Chart API会根据你将存储的信息的大小来决定使用哪个等级的QR码。最棒的QR码阅读器可以读取等级为40的QR码中存储的信息。然而通常来说移动设备最多可以读取等级为4的QR码中存储的信息。 下面来介绍使用PHP调取Google Chart API 来生成二维码 &#60;?php class qrcode {  private [...]]]></description>
			<content:encoded><![CDATA[<p>QRcode是二维码的一种。QRcode可以存储最多4296个字母数字类型的任意文本。这些文本可以是任何内容，例如，网址、联系信息、电话号码（具体科查看<a href="http://www.biaodianfu.com/encoding-of-information-in-barcodes.html">二维码数据格式</a>）。QR code存储的信息可以被安装有适当软件的光学设备读取。这种设备既可以是专用的QR code读取器也可以是手机。</p>
<p>通过调用 Google Chart Tools / Image Charts 的 API ，我们可以很方便的生成QRcode。</p>
<p>调用方式也很简单，只要向 http://chart.apis.google.com/chart 传入适合的参数就可以了，参数如下：</p>
<ol>
<li>cht=qr<br />
这个是必需的，告诉 API ，你需要生成的是二维码。</li>
<li>chs=&lt;<em>width</em>&gt;x&lt;<em>height</em>&gt;<br />
这个同样是必需的，告诉 API ，你需要生成的二维码的尺寸。</li>
<li>chl=&lt;<em>data</em>&gt;<br />
这个还是必需的，用来告诉 API 二维码所包含的信息。可以是数字、字符数字、字符、二进制信息、汉字。不能混合数据类型。数据必须经过UTF-8 URL-encoded。如果需要传递的信息超过2K个字节，请使用POST方式。</li>
<li>choe=&lt;<em>output_encoding</em>&gt;<br />
终于来了个不是必须的，这个是用来声明生成的二维码所包含信息的编码，默认是 UTF-8 ；其他可选编码是 Shift_JIS 、 ISO-8859-1</li>
<li>chld=&lt;<em>error_correction_level</em>&gt;|&lt;<em>margin</em>&gt;<br />
可选 纠错等级。QR码支持四个等级的纠错，用来恢复丢失的、读错的、模糊的、数据。下面是可选的值：L-(默认)可以识别已损失7%的数据；M-可以识别已损失15%的数据；Q-可以识别已损失25%的数据；H-可以识别已损失30%的数据。margin 是指生成的二维码离图片边框的距离。</li>
</ol>
<p>QR码是方形的，有相同的长和宽。QR码的大小是固定的：从21到177的长/宽，每次递增4个像素点。每个配置被称为一个等级。长和宽越大，存储的信息就越多。下面是版本摘要:</p>
<ul>
<li>等级为1的QR码长和宽分别为21个像素，最多可以存储25个字母数字和字符。</li>
<li>等级为2的QR码长和宽分别为25个像素，最多可以存储47个字母数字和字符。</li>
<li>…以此类推 。</li>
</ul>
<p>Chart API会根据你将存储的信息的大小来决定使用哪个等级的QR码。最棒的QR码阅读器可以读取等级为40的QR码中存储的信息。然而通常来说移动设备最多可以读取等级为4的QR码中存储的信息。</p>
<p>下面来介绍使用PHP调取Google Chart API 来生成二维码</p>
<pre lang="php" line="0" escaped="true">&lt;?php
class qrcode
{
 private $data;

 //creating text qr code
 public function text($text){
  $this-&gt;data = $text;
 }

 //creating code with link mtadata
 public function link($url){
  if (preg_match('/^http:\/\//', $url) || preg_match('/^https:\/\//', $url)) 
  {
   $this-&gt;data = $url;
  }
  else
  {
   $this-&gt;data = "<a href="http://&quot;.$url">http://".$url</a>;
  }
 }

 //creating code with bookmark metadata
 public function bookmark($title, $url){
  $this-&gt;data = "MEBKM:TITLE:".$title.";URL:".$url.";;";
 }

 //creating code with email address metadata
 public function email_address($email){
  $this-&gt;data = "<a href="mailto:&quot;.$email">MAILTO:".$email</a>;
 }

 //creating code with email metadata
 public function email($email, $subject, $message){
  $this-&gt;data = "MATMSG:TO:".$email.";SUB:".$subject.";BODY:".$message.";;";
 }

  //creating code with phone 
 public function phone_number($phone){
  $this-&gt;data = "TEL:".$phone;
 }

 //creating code with sms metadata
 public function sms($phone, $text){
  $this-&gt;data = "SMSTO:".$phone.":".$text;
 }

 //creating code with mms metadata
 public function mms($phone, $text){
  $this-&gt;data = "MMSTO:".$phone.":".$text;
 }

 //creating code with mecard metadata
 public function contact_info($name, $address, $phone, $email){
  $this-&gt;data = "MECARD:N:".$name.";ADR:".$address.";TEL:".$phone.";EMAIL:".$email.";;";
 }

 //creating code with geo location metadata
 public function geo($lat, $lon, $height){
  $this-&gt;data = "GEO:".$lat.",".$lon.",".$height;
 }

 //creating code with wifi configuration metadata
 public function wifi($type, $ssid, $pass){
  $this-&gt;data = "WIFI:T:".$type.";S".$ssid.";".$pass.";;";
 }

 //creating code with i-appli activating meta data
 public function iappli($adf, $cmd, $param){
  $cur = current($param);
  $next = next($param);
  $param_str = "";
  foreach($cur as $key =&gt; $val)
  {
   $param_str .= "PARAM:".$val.",".$next[$key].";";
  }
  $this-&gt;data = "LAPL:ADFURL:".$adf.";CMD:".$cmd.";".$param_str.";";
 }

 //creating code with gif or jpg image, or smf, MFi or ToruCa files as content
 public function content($type, $size, $content){
  $this-&gt;data = "CNTS:TYPE:".$type.";LNG:".$size.";BODY:".$content.";;";
 }

 //getting image
 public function get_image($size = 150, $EC_level = 'L', $margin = '0'){
  $ch = curl_init();
  $this-&gt;data = urlencode($this-&gt;data); 
  curl_setopt($ch, CURLOPT_URL, 'http://chart.apis.google.com/chart');
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_POSTFIELDS, 'chs='.$size.'x'.$size.'&amp;cht=qr&amp;chld='.$EC_level.'|'.$margin.'&amp;chl='.$this-&gt;data);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HEADER, false);
  curl_setopt($ch, CURLOPT_TIMEOUT, 30);

  $response = curl_exec($ch);
  curl_close($ch);
  return $response;
 }

 //getting link for image
 public function get_link($size = 150, $EC_level = 'L', $margin = '0'){
  $this-&gt;data = urlencode($this-&gt;data); 
  return 'http://chart.apis.google.com/chart?chs='.$size.'x'.$size.'&amp;cht=qr&amp;chld='.$EC_level.'|'.$margin.'&amp;chl='.$this-&gt;data;
 }

 //forsing image download
 public function download_image($file){

  header('Content-Description: File Transfer');
  header('Content-Type: image/png');
  header('Content-Disposition: attachment; filename=QRcode.png');
  header('Content-Transfer-Encoding: binary');
  header('Expires: 0');
  header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
  header('Pragma: public');
  header('Content-Length: ' . filesize($file));
  ob_clean();
  flush();
  echo $file;
 }
}
?&gt;</pre>
<p>使用上述二维码PHP类的方法：</p>
<pre lang="php" line="0" escaped="true">&lt;?php
include("qrcode.php");
$qr = new qrcode();
//bookmark
$title = "标点符";
$url = "http://www.biaodianfu.com/";
$qr-&gt;bookmark($title,$url);

//获取二维码图片URL
echo "&lt;img src='".$qr-&gt;get_link()."'&gt;";

//here is the way to output image
//header("Content-type:image/png");
//echo $qr-&gt;get_image();

//and here is the way to force image download
//$file = $qr-&gt;get_image();
//$qr-&gt;download_image($file)

?&gt;</pre>
<p>Related posts:<ol>
<li><a href='http://www.biaodianfu.com/php-api-framework.html' rel='bookmark' title='PHP API 框架开发的学习'>PHP API 框架开发的学习</a></li>
<li><a href='http://www.biaodianfu.com/php-curl-class.html' rel='bookmark' title='PHP 5 curl Class'>PHP 5 curl Class</a></li>
<li><a href='http://www.biaodianfu.com/js-css-chang-color.html' rel='bookmark' title='JS+CSS实现隔行换色'>JS+CSS实现隔行换色</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/google-chart-api-qrcode.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>来自于DeDeCMS的HTTP下载类</title>
		<link>http://www.biaodianfu.com/dede-cms-http-download-class.html</link>
		<comments>http://www.biaodianfu.com/dede-cms-http-download-class.html#comments</comments>
		<pubDate>Tue, 01 Feb 2011 14:03:25 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[下载]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=3223</guid>
		<description><![CDATA[无意间发现的一个Dedecms中的一些强大的下载类，适合文件下载，有的时候看一些开源的代码真的是能学到很多东西，加油~新的一年，学好PHP，让自己有一技之长。 此下载类貌似和snoopy.class.php有的一拼啊，没有仔细研究，先记下。 &#60;?php @set_time_limit(0); class DedeHttpDown {  var $m_url = '';  var $m_urlpath = '';  var $m_scheme = 'http';  var $m_host = '';  var $m_port = '80';  var $m_user = '';  var $m_pass = '';  var $m_path = '/';  var $m_query = '';  var $m_fp = '';  var $m_error = '';  var $m_httphead = ''; [...]]]></description>
			<content:encoded><![CDATA[<p>无意间发现的一个Dedecms中的一些强大的下载类，适合文件下载，有的时候看一些开源的代码真的是能学到很多东西，加油~新的一年，学好PHP，让自己有一技之长。</p>
<p>此下载类貌似和<a href="http://www.biaodianfu.com/snoopy-class-php.html">snoopy.class.php</a>有的一拼啊，没有仔细研究，先记下。</p>
<pre lang="php" line="0" escaped="true">
&lt;?php
@set_time_limit(0);

class DedeHttpDown
{
 var $m_url = '';
 var $m_urlpath = '';
 var $m_scheme = 'http';
 var $m_host = '';
 var $m_port = '80';
 var $m_user = '';
 var $m_pass = '';
 var $m_path = '/';
 var $m_query = '';
 var $m_fp = '';
 var $m_error = '';
 var $m_httphead = '';
 var $m_html = '';
 var $m_puthead = '';
 var $BaseUrlPath = '';
 var $HomeUrl = '';
 var $reTry = 0;
 var $JumpCount = 0;

 //初始化系统
 function PrivateInit($url)
 {
  if($url=='') {
   return ;
  }
  $urls = '';
  $urls = @parse_url($url);
  $this-&gt;m_url = $url;
  if(is_array($urls))
  {
   $this-&gt;m_host = $urls["host"];
   if(!empty($urls["scheme"]))
   {
    $this-&gt;m_scheme = $urls["scheme"];
   }
   if(!empty($urls["user"]))
   {
    $this-&gt;m_user = $urls["user"];
   }
   if(!empty($urls["pass"]))
   {
    $this-&gt;m_pass = $urls["pass"];
   }
   if(!empty($urls["port"]))
   {
    $this-&gt;m_port = $urls["port"];
   }
   if(!empty($urls["path"]))
   {
    $this-&gt;m_path = $urls["path"];
   }
   $this-&gt;m_urlpath = $this-&gt;m_path;
   if(!empty($urls["query"]))
   {
    $this-&gt;m_query = $urls["query"];
    $this-&gt;m_urlpath .= "?".$this-&gt;m_query;
   }
   $this-&gt;HomeUrl = $urls["host"];
   $this-&gt;BaseUrlPath = $this-&gt;HomeUrl.$urls["path"];
   $this-&gt;BaseUrlPath = preg_replace("/\/([^\/]*)\.(.*)$/","/",$this-&gt;BaseUrlPath);
   $this-&gt;BaseUrlPath = preg_replace("/\/$/","",$this-&gt;BaseUrlPath);
  }
 }

 function ResetAny()
 {
  //重设各参数
  $this-&gt;m_url = "";
  $this-&gt;m_urlpath = "";
  $this-&gt;m_scheme = "http";
  $this-&gt;m_host = "";
  $this-&gt;m_port = "80";
  $this-&gt;m_user = "";
  $this-&gt;m_pass = "";
  $this-&gt;m_path = "/";
  $this-&gt;m_query = "";
  $this-&gt;m_error = "";
 }

 //打开指定网址
 function OpenUrl($url,$requestType="GET")
 {
  $this-&gt;ResetAny();
  $this-&gt;JumpCount = 0;
  $this-&gt;m_httphead = Array() ;
  $this-&gt;m_html = '';
  $this-&gt;reTry = 0;
  $this-&gt;Close();

  //初始化系统
  $this-&gt;PrivateInit($url);
  $this-&gt;PrivateStartSession($requestType);
 }

 //转到303重定向网址
 function JumpOpenUrl($url)
 {
  $this-&gt;ResetAny();
  $this-&gt;JumpCount++;
  $this-&gt;m_httphead = Array() ;
  $this-&gt;m_html = "";
  $this-&gt;Close();

  //初始化系统
  $this-&gt;PrivateInit($url);
  $this-&gt;PrivateStartSession('GET');
 }

 //获得某操作错误的原因
 function printError()
 {
  echo "错误信息：".$this-&gt;m_error;
  echo "&lt;br/&gt;具体返回头：&lt;br/&gt;";
  foreach($this-&gt;m_httphead as $k=&gt;$v){ echo "$k =&gt; $v &lt;br/&gt;\r\n"; }
 }

 //判别用Get方法发送的头的应答结果是否正确
 function IsGetOK()
 {
  if( ereg("^2",$this-&gt;GetHead("http-state")) )
  {
   return true;
  }
  else
  {
   $this-&gt;m_error .= $this-&gt;GetHead("http-state")." - ".$this-&gt;GetHead("http-describe")."&lt;br/&gt;";
   return false;
  }
 }

 //看看返回的网页是否是text类型
 function IsText()
 {
  if( ereg("^2",$this-&gt;GetHead("http-state")) &amp;&amp; eregi("text|xml",$this-&gt;GetHead("content-type")) )
  {
   return true;
  }
  else
  {
   $this-&gt;m_error .= "内容为非文本类型或网址重定向&lt;br/&gt;";
   return false;
  }
 }

 //判断返回的网页是否是特定的类型
 function IsContentType($ctype)
 {
  if(ereg("^2",$this-&gt;GetHead("http-state"))
  &amp;&amp; $this-&gt;GetHead("content-type")==strtolower($ctype))
  { return true; }
  else
  {
   $this-&gt;m_error .= "类型不对 ".$this-&gt;GetHead("content-type")."&lt;br/&gt;";
   return false;
  }
 }

 //用Http协议下载文件
 function SaveToBin($savefilename)
 {
  if(!$this-&gt;IsGetOK())
  {
   return false;
  }
  if(@feof($this-&gt;m_fp))
  {
   $this-&gt;m_error = "连接已经关闭！"; return false;
  }
  $fp = fopen($savefilename,"w");
  while(!feof($this-&gt;m_fp))
  {
   fwrite($fp,fread($this-&gt;m_fp,1024));
  }
  fclose($this-&gt;m_fp);
  fclose($fp);
  return true;
 }

 //保存网页内容为Text文件
 function SaveToText($savefilename)
 {
  if($this-&gt;IsText())
  {
   $this-&gt;SaveBinFile($savefilename);
  }
  else
  {
   return "";
  }
 }

 //用Http协议获得一个网页的内容
 function GetHtml()
 {
  if(!$this-&gt;IsText())
  {
   return '';
  }
  if($this-&gt;m_html!='')
  {
   return $this-&gt;m_html;
  }
  if(!$this-&gt;m_fp||@feof($this-&gt;m_fp))
  {
   return '';
  }
  while(!feof($this-&gt;m_fp))
  {
   $this-&gt;m_html .= fgets($this-&gt;m_fp,256);
  }
  @fclose($this-&gt;m_fp);
  return $this-&gt;m_html;
 }

 //开始HTTP会话
 function PrivateStartSession($requestType="GET")
 {
  if(!$this-&gt;PrivateOpenHost())
  {
   $this-&gt;m_error .= "打开远程主机出错!";
   return false;
  }
  $this-&gt;reTry++;
  if($this-&gt;GetHead("http-edition")=="HTTP/1.1")
  {
   $httpv = "HTTP/1.1";
  }
  else
  {
   $httpv = "HTTP/1.0";
  }
  $ps = explode('?',$this-&gt;m_urlpath);

  $headString = '';

  //发送固定的起始请求头GET、Host信息
  if($requestType=="GET")
  {
   $headString .= "GET ".$this-&gt;m_urlpath." $httpv\r\n";
  }
  else
  {
   $headString .= "POST ".$ps[0]." $httpv\r\n";
  }
  $this-&gt;m_puthead["Host"] = $this-&gt;m_host;

  //发送用户自定义的请求头
  if(!isset($this-&gt;m_puthead["Accept"]))
  {
   $this-&gt;m_puthead["Accept"] = "*/*";
  }
  if(!isset($this-&gt;m_puthead["User-Agent"]))
  {
   $this-&gt;m_puthead["User-Agent"] = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2)";
  }
  if(!isset($this-&gt;m_puthead["Refer"]))
  {
   $this-&gt;m_puthead["Refer"] = "http://".$this-&gt;m_puthead["Host"];
  }

  foreach($this-&gt;m_puthead as $k=&gt;$v)
  {
   $k = trim($k);
   $v = trim($v);
   if($k!=""&amp;&amp;$v!="")
   {
    $headString .= "$k: $v\r\n";
   }
  }
  fputs($this-&gt;m_fp, $headString);
  if($requestType=="POST")
  {
   $postdata = "";
   if(count($ps)&gt;1)
   {
    for($i=1;$i&lt;count($ps);$i++)
    {
     $postdata .= $ps[$i];
    }
   }
   else
   {
    $postdata = "OK";
   }
   $plen = strlen($postdata);
   fputs($this-&gt;m_fp,"Content-Type: application/x-www-form-urlencoded\r\n");
   fputs($this-&gt;m_fp,"Content-Length: $plen\r\n");
  }

  //发送固定的结束请求头
  //HTTP1.1协议必须指定文档结束后关闭链接,否则读取文档时无法使用feof判断结束
  if($httpv=="HTTP/1.1")
  {
   fputs($this-&gt;m_fp,"Connection: Close\r\n\r\n");
  }
  else
  {
   fputs($this-&gt;m_fp,"\r\n");
  }
  if($requestType=="POST")
  {
   fputs($this-&gt;m_fp,$postdata);
  }

  //获取应答头状态信息
  $httpstas = explode(" ",fgets($this-&gt;m_fp,256));
  $this-&gt;m_httphead["http-edition"] = trim($httpstas[0]);
  $this-&gt;m_httphead["http-state"] = trim($httpstas[1]);
  $this-&gt;m_httphead["http-describe"] = "";
  for($i=2;$i&lt;count($httpstas);$i++)
  {
   $this-&gt;m_httphead["http-describe"] .= " ".trim($httpstas[$i]);
  }

  //获取详细应答头
  while(!feof($this-&gt;m_fp))
  {
   $line = trim(fgets($this-&gt;m_fp,256));
   if($line == "")
   {
    break;
   }
   $hkey = "";
   $hvalue = "";
   $v = 0;
   for($i=0;$i&lt;strlen($line);$i++)
   {
    if($v==1)
    {
     $hvalue .= $line[$i];
    }
    if($line[$i]==":")
    {
     $v = 1;
    }
    if($v==0)
    {
     $hkey .= $line[$i];
    }
   }
   $hkey = trim($hkey);
   if($hkey!="")
   {
    $this-&gt;m_httphead[strtolower($hkey)] = trim($hvalue);
   }
  }

  //如果连接被不正常关闭，重试
  if(feof($this-&gt;m_fp))
  {
   if($this-&gt;reTry &gt; 10)
   {
    return false;
   }
   $this-&gt;PrivateStartSession($requestType);
  }

  //判断是否是3xx开头的应答
  if(ereg("^3",$this-&gt;m_httphead["http-state"]))
  {
   if($this-&gt;JumpCount &gt; 3)
   {
    return;
   }
   if(isset($this-&gt;m_httphead["location"]))
   {
    $newurl = $this-&gt;m_httphead["location"];
    if(eregi("^http",$newurl))
    {
     $this-&gt;JumpOpenUrl($newurl);
    }
    else
    {
     $newurl = $this-&gt;FillUrl($newurl);
     $this-&gt;JumpOpenUrl($newurl);
    }
   }
   else
   {
    $this-&gt;m_error = "无法识别的答复！";
   }
  }
 }

 //获得一个Http头的值
 function GetHead($headname)
 {
  $headname = strtolower($headname);
  return isset($this-&gt;m_httphead[$headname]) ? $this-&gt;m_httphead[$headname] : '';
 }

 //设置Http头的值
 function SetHead($skey,$svalue)
 {
  $this-&gt;m_puthead[$skey] = $svalue;
 }

 //打开连接
 function PrivateOpenHost()
 {
  if($this-&gt;m_host=="")
  {
   return false;
  }
  $errno = "";
  $errstr = "";
  $this-&gt;m_fp = @fsockopen($this-&gt;m_host, $this-&gt;m_port, $errno, $errstr,10);
  if(!$this-&gt;m_fp)
  {
   $this-&gt;m_error = $errstr;
   return false;
  }
  else
  {
   return true;
  }
 }

 //关闭连接
 function Close()
 {
  @fclose($this-&gt;m_fp);
 }

 //补全相对网址
 function FillUrl($surl)
 {
  $i = 0;
  $dstr = "";
  $pstr = "";
  $okurl = "";
  $pathStep = 0;
  $surl = trim($surl);
  if($surl=="")
  {
   return "";
  }
  $pos = strpos($surl,"#");
  if($pos&gt;0)
  {
   $surl = substr($surl,0,$pos);
  }
  if($surl[0]=="/")
  {
   $okurl = "http://".$this-&gt;HomeUrl.$surl;
  }
  else if($surl[0]==".")
  {
   if(strlen($surl)&lt;=1)
   {
    return "";
   }
   else if($surl[1]=="/")
   {
    $okurl = "http://".$this-&gt;BaseUrlPath."/".substr($surl,2,strlen($surl)-2);
   }
   else
   {
    $urls = explode("/",$surl);
    foreach($urls as $u)
    {
     if($u=="..")
     {
      $pathStep++;
     }
     else if($i&lt;count($urls)-1)
     {
      $dstr .= $urls[$i]."/";
     }
     else
     {
      $dstr .= $urls[$i];
     }
     $i++;
    }
    $urls = explode("/",$this-&gt;BaseUrlPath);
    if(count($urls) &lt;= $pathStep)
    {
     return "";
    }
    else
    {
     $pstr = "http://";
     for($i=0;$i&lt;count($urls)-$pathStep;$i++)
     {
      $pstr .= $urls[$i]."/";
     }
     $okurl = $pstr.$dstr;
    }
   }
  }
  else
  {
   if(strlen($surl)&lt;7)
   {
    $okurl = "http://".$this-&gt;BaseUrlPath."/".$surl;
   }
   else if(strtolower(substr($surl,0,7))=="http://")
   {
    $okurl = $surl;
   }
   else
   {
    $okurl = "http://".$this-&gt;BaseUrlPath."/".$surl;
   }
  }
  $okurl = eregi_replace("^(http://)","",$okurl);
  $okurl = eregi_replace("/{1,}","/",$okurl);
  return "http://".$okurl;
 }
}

?&gt;
</pre>
<p>使用方法如下：</p>
<pre lang="php" line="0" escaped="true">
&lt;?php
// 下载网页
$httpdown = new DedeHttpDown();
$httpdown-&gt;OpenUrl(http://www.dedecms.com/);
echo $httpdown-&gt;GetHtml();
$httpdown-&gt;Close();
?&gt;
</pre>
<pre lang="php" line="0" escaped="true">
&lt;?php
//如果下载图片并保存,可以用
$httpdown = new DedeHttpDown();
$httpdown-&gt;OpenUrl("http://www.dedecms.com/logo.jpg");
echo $httpdown-&gt;SaveBin("dedecms-logo.jpg");
$httpdown-&gt;Close();
echo "&lt;img src='dedecms-logo.jpg'&gt;";
?&gt;
</pre>
<p>Related posts:<ol>
<li><a href='http://www.biaodianfu.com/php-spider-log.html' rel='bookmark' title='PHP版记录蜘蛛爬行历史'>PHP版记录蜘蛛爬行历史</a></li>
<li><a href='http://www.biaodianfu.com/php-curl-class.html' rel='bookmark' title='PHP 5 curl Class'>PHP 5 curl Class</a></li>
<li><a href='http://www.biaodianfu.com/goo-gl-php-api.html' rel='bookmark' title='Goo.gl PHP API'>Goo.gl PHP API</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/dede-cms-http-download-class.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP自动保存文章中的外部图片</title>
		<link>http://www.biaodianfu.com/php-get-remote-pic.html</link>
		<comments>http://www.biaodianfu.com/php-get-remote-pic.html#comments</comments>
		<pubDate>Tue, 01 Feb 2011 13:12:59 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[图片]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=3220</guid>
		<description><![CDATA[对于一些转载性质或采集性质的网站，因为外链图片有可能被来源网站删除或出现防盗链的情况，有的时候站长期望把文章中的外联图片都保存到本地空间里。解决办法是在原有的系统中添加一个PHP自动保存文章中外部图片的功能。 首先我们想到的是可以通过正则匹配来寻找文章中所有的img标签，这个表达式需要可以匹配跨行的img标签，并且需要对img标签做条件判断允许img标签带有其他属性.解决方案是使用preg_replace_callback() 这个函数。 function getRomatePic($data){         $pattern = '/&#60;img[^\/]*src=\"([^\"]*)\"[^\/]*\/&#62;/ims';          return preg_replace_callback($pattern,filter_image_call, $data);     }  为了完成外部图片的链接过滤及图片的本地保存，定义filter_image_call()。 function filter_image_call($match){                 $postImagePath="pic/";                 $postImageUrlBase="http://localhost/pic/";         $image_tag = $match[0]; //获得匹配的img标签         $image_url = $match[1]; //匹配img标签的src属性值         //如果src属性值不是http://开头的，也就是说图片已经是本地地址，不做任何修改而返回原始的img标签         if(substr($image_url, 0, 7) != 'http://'){                 return $image_tag;         }         $postfix = date('Y-m-d');         $dir_prefix = $postImagePath.$postfix."/"; //预定义的本地保存图片的文件夹，根据需要改变         $url_prefix [...]]]></description>
			<content:encoded><![CDATA[<p>对于一些转载性质或采集性质的网站，因为外链图片有可能被来源网站删除或出现防盗链的情况，有的时候站长期望把文章中的外联图片都保存到本地空间里。解决办法是在原有的系统中添加一个PHP自动保存文章中外部图片的功能。</p>
<p>首先我们想到的是可以通过正则匹配来寻找文章中所有的img标签，这个表达式需要可以匹配跨行的img标签，并且需要对img标签做条件判断允许img标签带有其他属性.解决方案是使用preg_replace_callback() 这个函数。</p>
<pre lang="php" line="0" escaped="true">function getRomatePic($data){
        $pattern = '/&lt;img[^\/]*src=\"([^\"]*)\"[^\/]*\/&gt;/ims'; 
        return preg_replace_callback($pattern,filter_image_call, $data);    
}</pre>
<p> 为了完成外部图片的链接过滤及图片的本地保存，定义filter_image_call()。</p>
<pre lang="php" line="0" escaped="true">function filter_image_call($match){
                $postImagePath="pic/";
                $postImageUrlBase="http://localhost/pic/";
        $image_tag = $match[0]; //获得匹配的img标签
        $image_url = $match[1]; //匹配img标签的src属性值
        //如果src属性值不是http://开头的，也就是说图片已经是本地地址，不做任何修改而返回原始的img标签
        if(substr($image_url, 0, 7) != 'http://'){
                return $image_tag;
        }
        $postfix = date('Y-m-d');
        $dir_prefix = $postImagePath.$postfix."/"; //预定义的本地保存图片的文件夹，根据需要改变
        $url_prefix = $postImageUrlBase.$postfix."/"; //预定义的url前缀，根据需要改变
        //echo $url_prefix;
        //新建保存图片的文件夹
        if(!file_exists($dir_prefix)){
                mkdir($dir_prefix, 0777, true);
        }
        //随机生成图片文件名
        $arr = split("[/\\.]", $image_url);
        $ext =  '.'.$arr[count($arr) - 1];
        $file_name = substr(sha1(date('Y-m-d H:i:s') . rand(0,1000)), 0, 5) .rand_str(5) . $ext;
        //使用http_get_file函数得到远程图片文件，并保存到本地
        $file = http_get_file($image_url);
  file_put_contents($dir_prefix . $file_name, $file);
        //通过str_replace函数替换掉原始img标签中的src属性/
        return str_replace($image_url, $url_prefix . $file_name, $image_tag);
}</pre>
<p>最后，定义获取图片的函数，<a href="http://www.biaodianfu.com/discuz-dfopen-fsockopen.html">也可以使用discuz中的dfopen函数</a>。</p>
<pre lang="php" line="0" escaped="true">function http_get_file($url){
        $url_stuff = parse_url($url);
        $port = isset($url_stuff['port']) ? $url_stuff['port']:80;
        $fp = fsockopen($url_stuff['host'], $port);
        $query  = 'GET ' . $url_stuff['path'] . " HTTP/1.0\n";
        $query .= 'Host: ' . $url_stuff['host'];
        $query .= "\n\n";
        fwrite($fp, $query);
        while ($line = fread($fp, 1024)) {
                $buffer .= $line;
        }
        preg_match('/Content-Length: ([0-9]+)/', $buffer, $parts);
        return substr($buffer, - $parts[1]);
}</pre>
<p>另外中间会用到的还有生成指点数目的随机字符串：</p>
<pre lang="php" line="0" escaped="true">function rand_str($size=6,$feed="abcdefghijklmnopqrstuvwxyz0123456789"){
                for ($i=0; $i &lt; $size; $i++) {
                                $rand_str .= substr($feed, rand() % strlen($feed), 1);
                }
           return $rand_str;
        }</pre>
<p>整个方法如上，高手可以将上述修改为 WordPress插件，Discuz插件，PHPWind插件等。</p>
<p>Related posts:<ol>
<li><a href='http://www.biaodianfu.com/discuz-dfopen-fsockopen.html' rel='bookmark' title='dfopen()：discuz封装的fsockopen()'>dfopen()：discuz封装的fsockopen()</a></li>
<li><a href='http://www.biaodianfu.com/php-idcard-checksum.html' rel='bookmark' title='PHP身份证校验'>PHP身份证校验</a></li>
<li><a href='http://www.biaodianfu.com/smarty-intercept-chinese-char.html' rel='bookmark' title='Smarty截取中文乱码的解决办法'>Smarty截取中文乱码的解决办法</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/php-get-remote-pic.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>新浪t.cn短网址接口(PHP)</title>
		<link>http://www.biaodianfu.com/sina-shortener-url-api-php.html</link>
		<comments>http://www.biaodianfu.com/sina-shortener-url-api-php.html#comments</comments>
		<pubDate>Tue, 01 Feb 2011 12:30:59 +0000</pubDate>
		<dc:creator>标点符</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[短网址]]></category>

		<guid isPermaLink="false">http://www.biaodianfu.com/?p=3213</guid>
		<description><![CDATA[接上一篇文章 Google 短网址 API PHP版，新浪t.cn的短网址一般情况下不易被某些机构OOXX，所以比较适合我们这些善良的中国网民哈。下面是我随便捣鼓的PHP版的新浪短网址方法。 同样使用前要先去申请API-KEY，或使用oAuth进行认证。 将长网址变为短网址的方法： &#60;?php function shortenSinaUrl($long_url){  $apiKey='API-KEY';  $apiUrl='http://api.t.sina.com.cn/short_url/shorten.json?source='.$apiKey.'&#38;url_long='.$long_url;  $curlObj = curl_init();  curl_setopt($curlObj, CURLOPT_URL, $apiUrl);  curl_setopt($curlObj, CURLOPT_RETURNTRANSFER, 1);  curl_setopt($curlObj, CURLOPT_SSL_VERIFYPEER, 0);  curl_setopt($curlObj, CURLOPT_HEADER, 0);  curl_setopt($curlObj, CURLOPT_HTTPHEADER, array('Content-type:application/json'));  $response = curl_exec($curlObj);  curl_close($curlObj);  $json = json_decode($response);  return $json[0]-&#62;url_short; } ?&#62; 将短网址还原成长网址的方法： &#60;?php function expandSinaUrl($short_url){  $apiKey='API-KEY';  $apiUrl='http://api.t.sina.com.cn/short_url/expand.json?source='.$apiKey.'&#38;url_short='.$short_url;  $curlObj = curl_init();  curl_setopt($curlObj, CURLOPT_URL, $apiUrl);  curl_setopt($curlObj, CURLOPT_RETURNTRANSFER, [...]]]></description>
			<content:encoded><![CDATA[<p>接上一篇文章 <a href="http://www.biaodianfu.com/php-google-urlshortener-api.html">Google 短网址 API PHP版</a>，新浪t.cn的短网址一般情况下不易被某些机构OOXX，所以比较适合我们这些善良的中国网民哈。下面是我随便捣鼓的PHP版的新浪短网址方法。</p>
<p>同样使用前要先去申请API-KEY，或使用oAuth进行认证。</p>
<p>将长网址变为短网址的方法：</p>
<pre lang="php" line="0" escaped="true">
&lt;?php
function shortenSinaUrl($long_url){
 $apiKey='API-KEY';
 $apiUrl='http://api.t.sina.com.cn/short_url/shorten.json?source='.$apiKey.'&amp;url_long='.$long_url;
 $curlObj = curl_init();
 curl_setopt($curlObj, CURLOPT_URL, $apiUrl);
 curl_setopt($curlObj, CURLOPT_RETURNTRANSFER, 1);
 curl_setopt($curlObj, CURLOPT_SSL_VERIFYPEER, 0);
 curl_setopt($curlObj, CURLOPT_HEADER, 0);
 curl_setopt($curlObj, CURLOPT_HTTPHEADER, array('Content-type:application/json'));
 $response = curl_exec($curlObj);
 curl_close($curlObj);
 $json = json_decode($response);
 return $json[0]-&gt;url_short;
}
?&gt;
</pre>
<p>将短网址还原成长网址的方法：</p>
<pre lang="php" line="0" escaped="true">&lt;?php
function expandSinaUrl($short_url){
 $apiKey='API-KEY';
 $apiUrl='http://api.t.sina.com.cn/short_url/expand.json?source='.$apiKey.'&amp;url_short='.$short_url;
 $curlObj = curl_init();
 curl_setopt($curlObj, CURLOPT_URL, $apiUrl);
 curl_setopt($curlObj, CURLOPT_RETURNTRANSFER, 1);
 curl_setopt($curlObj, CURLOPT_SSL_VERIFYPEER, 0);
 curl_setopt($curlObj, CURLOPT_HEADER, 0);
 curl_setopt($curlObj, CURLOPT_HTTPHEADER, array('Content-type:application/json'));
 $response = curl_exec($curlObj);
 curl_close($curlObj);
 $json = json_decode($response);
 return $json[0]-&gt;url_long;
}
?&gt;</pre>
<p>更多具体使用方法，请参见：<a href="http://open.t.sina.com.cn/wiki/index.php/Short_url/expand">http://open.t.sina.com.cn/wiki/index.php/Short_url/expand</a></p>
<p>Related posts:<ol>
<li><a href='http://www.biaodianfu.com/php-google-urlshortener-api.html' rel='bookmark' title='最新Google短网址API（PHP）'>最新Google短网址API（PHP）</a></li>
<li><a href='http://www.biaodianfu.com/goo-gl-php-api.html' rel='bookmark' title='Goo.gl PHP API'>Goo.gl PHP API</a></li>
<li><a href='http://www.biaodianfu.com/php-curl-class.html' rel='bookmark' title='PHP 5 curl Class'>PHP 5 curl Class</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://www.biaodianfu.com/sina-shortener-url-api-php.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

