2012年4月22日星期日

Git安装使用

Linux端

到工作目录
cd project

创建git版本库
git init

往版本库添加文件
git add .
git commit -m "git init"



Win端

克隆一个库
git clone ssh://user@localhost:port/porject/

git pull


2012年4月11日星期三

PHP与MYSQL事务处理

PHP与MYSQL事务处理
/*
MYSQL的事务处理主要有两种方法。
1、用begin,rollback,commit来实现
begin 开始一个事务
rollback 事务回滚
commit 事务确认
2、直接用set来改变mysql的自动提交模式
MYSQL默认是自动提交的,也就是你提交一个QUERY,它就直接执行!我们可以通过
set autocommit=0 禁止自动提交
set autocommit=1 开启自动提交
来实现事务的处理。
当你用 set autocommit=0 的时候,你以后所有的SQL都将做为事务处理,直到你用commit确认或rollback结束。
注意当你结束这个事务的同时也开启了个新的事务!按第一种方法只将当前的作为一个事务!
个人推荐使用第一种方法!
MYSQL中只有INNODB和BDB类型的数据表才能支持事务处理!其他的类型是不支持的!
***:一般MYSQL数据库默认的引擎是MyISAM,这种引擎不支持事务!如果要让MYSQL支持事务,可以自己手动修改:
方法如下:1.修改c:\appserv\mysql\my.ini文件,找到skip-InnoDB,在前面加上#,后保存文件。
2.在运行中输入:services.msc,重启mysql服务。
3.到phpmyadmin中,mysql->show engines;(或执行mysql->show variables like 'have_%'; ),查看InnoDB为YES,即表示数据库支持InnoDB了。
也就说明支持事务transaction了。
4.在创建表时,就可以为Storage Engine选择InnoDB引擎了。如果是以前创建的表,可以使用mysql->alter table table_name type=InnoDB;
或 mysql->alter table table_name engine=InnoDB;来改变数据表的引擎以支持事务。
*/
/*************** transaction--1 ***************/
$conn = mysql_connect('localhost','root','root') or die ("数据连接错误!!!");
mysql_select_db('test',$conn);
mysql_query("set names 'GBK'"); //使用GBK中文编码;
//开始一个事务
mysql_query("BEGIN"); //或者mysql_query("START TRANSACTION");
$sql = "INSERT INTO `user` (`id`, `username`, `sex`) VALUES (NULL, 'test1', '0')";
$sql2 = "INSERT INTO `user` (`did`, `username`, `sex`) VALUES (NULL, 'test1', '0')";//这条我故意写错
$res = mysql_query($sql);
$res1 = mysql_query($sql2); 
if($res && $res1){
mysql_query("COMMIT");
echo '提交成功。';
}else{
mysql_query("ROLLBACK");
echo '数据回滚。';
}
mysql_query("END"); 
/**************** transaction--2 *******************/
/*方法二*/
mysql_query("SET AUTOCOMMIT=0"); //设置mysql不自动提交,需自行用commit语句提交
$sql = "INSERT INTO `user` (`id`, `username`, `sex`) VALUES (NULL, 'test1', '0')";
$sql2 = "INSERT INTO `user` (`did`, `username`, `sex`) VALUES (NULL, 'test1', '0')";//这条我故意写错
$res = mysql_query($sql);
$res1 = mysql_query($sql2); 
if($res && $res1){
mysql_query("COMMIT");
echo '提交成功。';
}else{
mysql_query("ROLLBACK");
echo '数据回滚。';
}
mysql_query("END"); //事务处理完时别忘记mysql_query("SET AUTOCOMMIT=1");自动提交


/******************对于不支持事务的MyISAM引擎数据库可以使用表锁定的方法:********************/


//MyISAM & InnoDB 都支持,
/*
LOCK TABLES可以锁定用于当前线程的表。如果表被其它线程锁定,则造成堵塞,直到可以获取所有锁定为止。
UNLOCK TABLES可以释放被当前线程保持的任何锁定。当线程发布另一个LOCK TABLES时,或当与服务器的连接被关闭时,所有由当前线程锁定的表被隐含地解锁。
*/

mysql_query("LOCK TABLES `user` WRITE");//锁住`user`表
$sql = "INSERT INTO `user` (`id`, `username`, `sex`) VALUES (NULL, 'test1', '0')";
$res = mysql_query($sql);
if($res){
echo '提交成功。!';
}else{
echo '失败!';
}
mysql_query("UNLOCK TABLES");//解除锁定
MyISAM 是MySQL中默认的存储引擎,一般来说不是有太多人关心这个东西。决定使用什么样的存储引擎是一个很tricky的事情,但是还是值我们去研究一下,这里的文章只考虑 MyISAM 和InnoDB这两个,因为这两个是最常见的。   

下面先让我们回答一些问题:   
◆你的数据库有外键吗?   
◆你需要事务支持吗?   
◆你需要全文索引吗?   
◆你经常使用什么样的查询模式?   
◆你的数据有多大?   

myisam只有索引缓存   

innodb不分索引文件数据文件 innodb buffer   

myisam只能管理索引,在索引数据大于分配的资源时,会由操作系统来cache;数据文件依赖于操作系统的cache。innodb不管是索引还是数据,都是自己来管理   

思考上面这些问题可以让你找到合适的方向,但那并不是绝对的。如果你需要事务处理或是外键,那么InnoDB 可能是比较好的方式。如果你需要全文索引,那么通常来说 MyISAM是好的选择,因为这是系统内建的,然而,我们其实并不会经常地去测试两百万行记录。所以,就算是慢一点,我们可以通过使用Sphinx从 InnoDB中获得全文索引。   

数据的大小,是一个影响你选择什么样存储引擎的重要因素,大尺寸的数据集趋向于选择InnoDB方式,因为其支持事务处理和故障恢复。数据库的在小决定了 故障恢复的时间长短,InnoDB可以利用事务日志进行数据恢复,这会比较快。而MyISAM可能会需要几个小时甚至几天来干这些事,InnoDB只需要 几分钟。   

您操作数据库表的习惯可能也会是一个对性能影响很大的因素。比如: COUNT() 在 MyISAM 表中会非常快,而在InnoDB 表下可能会很痛苦。而主键查询则在InnoDB下会相当相当的快,但需要小心的是如果我们的主键太长了也会导致性能问题。大批的inserts 语句在 MyISAM下会快一些,但是updates 在InnoDB 下会更快一些——尤其在并发量大的时候。   

所以,到底你检使用哪一个呢?根据经验来看,如果是一些小型的应用或项目,那么MyISAM 也许会更适合。当然,在大型的环境下使用 MyISAM 也会有很大成功的时候,但却不总是这样的。如果你正在计划使用一个超大数据量的项目,而且需要事务处理或外键支持,那么你真的应该直接使用 InnoDB方式。但需要记住InnoDB 的表需要更多的内存和存储,转换100GB 的MyISAM 表到InnoDB 表可能会让你有非常坏的体验。   

===========================================================   

MyISAM:这个是默认类型,它是基于传统的ISAM类型,ISAM是 Indexed Sequential Access Method (有索引的顺序访问方法) 的缩写,它是存储记录和文件的标准方法.与其他存储引擎比较,MyISAM具有检查和修复表格的大多数工具. MyISAM表格可以被压缩,而且它们支持全文搜索.它们不是事务安全的,而且也不支持外键。如果事物回滚将造成不完全回滚,不具有原子性。如果执行大量 的SELECT,MyISAM是更好的选择。   

InnoDB:这种类型是事务安全的.它与BDB类型具有相同的特性,它们还支持外键.InnoDB表格速度很快.具有比BDB还丰富的特性,因此如果需 要一个事务安全的存储引擎,建议使用它.如果你的数据执行大量的INSERT或UPDATE,出于性能方面的考虑,应该使用InnoDB表,   

对于支持事物的InnoDB类型的标,影响速度的主要原因是AUTOCOMMIT默认设置是打开的,而且程序没有显式调用BEGIN 开始事务,导致每插入一条都自动Commit,严重影响了速度。可以在执行sql前调用begin,多条sql形成一个事物(即使autocommit打 开也可以),将大大提高性能。   

===============================================================   

InnoDB和MyISAM是在使用MySQL最常用的两个表类型,各有优缺点,视具体应用而定。下面是已知的两者之间的差别,仅供参考。   

innodb   
InnoDB 给 MySQL 提供了具有事务(commit)、回滚(rollback)和崩溃修复能力 (crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。 InnoDB 提供了行锁(locking on row level),提供与 Oracle 类型一致的不加锁读取(non- locking read in SELECTs)。这些特性均提高了多用户并发操作的性能表现。在InnoDB表中不需要扩大锁定 (lock escalation),因为 InnoDB 的列锁定(row level locks)适宜非常小的空间。 InnoDB 是 MySQL 上第一个提供外键约束(FOREIGN KEY constraints)的表引擎。   

InnoDB 的设计目标是处理大容量数据库系统,它的 CPU 利用率是其它基于磁盘的关系数据库引擎所不能比的。在技术上,InnoDB 是一套放在 MySQL 后台的完整数据库系统,InnoDB 在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。 InnoDB 把数据和索引存放在表空间里,可能包含多个文件,这与其它的不一样,举例来说,在 MyISAM 中,表被存放在单独的文件中。InnoDB 表的大小只受限于操作系统的文件大小,一般为 2 GB。   
InnoDB所有的表都保存在同一个数据文件 ibdata1 中(也可能是多个文件,或者是独立的表空间文件),相对来说比较不好备份,免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump。   


MyISAM   
MyISAM 是MySQL缺省存贮引擎 .   

每张MyISAM 表被存放在三个文件 。frm 文件存放表格定义。 数据文件是MYD (MYData) 。 索引文件是 MYI (MYIndex) 引伸。   

因为MyISAM相对简单所以在效率上要优于InnoDB..小型应用使用MyISAM是不错的选择.   

MyISAM表是保存成文件的形式,在跨平台的数据转移中使用MyISAM存储会省去不少的麻烦   

以下是一些细节和具体实现的差别:   

1.InnoDB不支持FULLTEXT类型的索引。   
2.InnoDB 中不保存表的具体行数,也就是说,执行select count(*) from table时,InnoDB要扫描一遍整个表来计算有多少行,但是MyISAM只要简单的读出保存好的行数即可。注意的是,当count(*)语句包含 where条件时,两种表的操作是一样的。   
3.对于AUTO_INCREMENT类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。   
4.DELETE FROM table时,InnoDB不会重新建立表,而是一行一行的删除。   
5.LOAD TABLE FROM MASTER操作对InnoDB是不起作用的,解决方法是首先把InnoDB表改成MyISAM表,导入数据后再改成InnoDB表,但是对于使用的额外的InnoDB特性(例如外键)的表不适用。   

另外,InnoDB表的行锁也不是绝对的,如果在执行一个SQL语句时MySQL不能确定要扫描的范围,InnoDB表同样会锁全表,例如 update table set num=1 where name like “%aaa%”   

任何一种表都不是万能的,只用恰当的针对业务类型来选择合适的表类型,才能最大的发挥MySQL的性能优势。   

===============================================================   


以下是InnoDB和MyISAM的一些联系和区别!   

1. 4.0以上mysqld都支持事务,包括非max版本。3.23的需要max版本mysqld才能支持事务。   

2. 创建表时如果不指定type则默认为myisam,不支持事务。   
可以用 show create table tablename 命令看表的类型。   

2.1 对不支持事务的表做start/commit操作没有任何效果,在执行commit前已经提交,测试:   
执行一个msyql:   
use test;   
drop table if exists tn;   
create table tn (a varchar(10)) type=myisam;   
drop table if exists ty;   
create table ty (a varchar(10)) type=innodb;   

begin;   
insert into tn values(‘a’);   
insert into ty values(‘a’);   
select * from tn;   
select * from ty;   
都能看到一条记录   

执行另一个mysql:   
use test;   
select * from tn;   
select * from ty;   
只有tn能看到一条记录   
然后在另一边   
commit;   
才都能看到记录。   

3. 可以执行以下命令来切换非事务表到事务(数据不会丢失),innodb表比myisam表更安全:   
   alter table tablename type=innodb;   

3.1 innodb表不能用repair table命令和myisamchk -r table_name   
但可以用check table,以及mysqlcheck [OPTIONS] database [tables]   

==============================================================   

mysql中使用select for update的必须针对InnoDb,并且是在一个事务中,才能起作用。   

select的条件不一样,采用的是行级锁还是表级锁也不一样。   
http://www.neo.com.tw/archives/900 的说明   

由于InnoDB 预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL 才会执行Row lock (只锁住被选取的资料例) ,否则MySQL 将会执行Table Lock (将整个资料表单给锁住)。   


举个例子:   

假设有个表单products ,里面有id 跟name 二个栏位,id 是主键。   

例1: (明确指定主键,并且有此笔资料,row lock)   

SELECT * FROM products WHERE id=’3′ FOR UPDATE;   

例2: (明确指定主键,若查无此笔资料,无lock)   

SELECT * FROM products WHERE id=’-1′ FOR UPDATE;   

例2: (无主键,table lock)   

SELECT * FROM products WHERE name=’Mouse’ FOR UPDATE;   

例3: (主键不明确,table lock)   

SELECT * FROM products WHERE id<>’3′ FOR UPDATE;   

例4: (主键不明确,table lock)   

SELECT * FROM products WHERE id LIKE ’3′ FOR UPDATE;   

注1:   
FOR UPDATE 仅适用于InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效

php随机算法不够随机啊


function run() {
  $j = 1;
  for ($i = 1; $i <= 50000; $i++) {
    $init = rou_init();
    $row = array_rand($init, 1);

    $last = isset($that) ? $that : 'start';
    $that = $row % 2;
    if ($last === $that || $row == 0) {
      $j++;
    } else {
      $res['all-times'][$j]++;

      if ($j == 9) {
        if (isset($k) && $k != $i) {
          var_dump($i, $k);
          $res['all-times'][$j . 'times'][] = $i - $k;
        }
        $k = $i;
      }

      $j = 1;
    }

    if ($that == 1) {
      $res['odd'] += 1;
    } else {
      $res['even'] += 1;
    }
  }
  ksort($res['all-times']);
  return $res;
}
?>

array
  'all-times' => 
    array
      1 => int 12478
      2 => int 5602
      3 => int 3055
      4 => int 1868
      5 => int 764
      6 => int 509
      7 => int 85
      8 => int 85
      9 => int 170
  'odd' => int 23681
  'even' => int 26319



var_dump($i, $k);
出来的虽然每次都不一样
 $res['all-times'][$j . 'times'][] = $i - $k;
但是 $i - $k 都是固定的几个数字 

看来得研究php源码的随机算法

2012年4月5日星期四

PHP扩展编写PHP Extension Writing(Sara Golemon)


原文:http://devzone.zend.com/public/view/tag/Extension
Part I: Introduction to PHP and Zend
编写扩展I -  PHP和Zend起步
http://devzone.zend.com/article/1021-Extension-Writing-Part-I-Introduction-to-PHP-and-Zend
Part II: Parameters, Arrays, and ZVALs
编写扩展_II - 参数、数组和ZVALs
http://devzone.zend.com/article/1022-Extension-Writing-Part-II-Parameters-Arrays-and-ZVALs
Part II: Parameters, Arrays, and ZVALs [continued]
编写扩展_II - 参数、数组和ZVALs[继续]
http://devzone.zend.com/article/1023-Extension-Writing-Part-II-Parameters-Arrays-and-ZVALs-continued
Part III: Resources
编写扩展_III - 资源
http://devzone.zend.com/article/1024-Extension-Writing-Part-III-Resources

编写扩展 I:PHP和Zend起步
扩展   教程  
by Sara Golemon | Monday, February 28, 2005

介绍
扩展是什么?
生存周期
内存分配
建立构建环境
Hello World
构建你的扩展
初始设置(INI)
全局数值
初始设置(INI)作为全局数值
核对(代码)完整性
下一步是什么?

介绍
既然您正在阅读本教程,那么您或许对编写PHP语言的扩展感兴趣。如果不是...呃,或许你并不知道这一兴趣,那么我们结束的时候你就会发现它。
本教程假定您基本熟悉PHP语言及其解释器实现所用的语言:C.
让我们从指明为什么你想要编写PHP扩展开始。
限于PHP语言本身的抽象程度,它不能直接访问某些库或特定于操作系统的调用。
你想要通过某些不平常的方法定制PHP的行为。
你有一些现成的PHP代码,但是你知道它可以(运行)更快、(占用空间)更小,而且消耗更少的内存。
你有一些不错的代码出售,买家可以使用它,但重要的是不能看到源代码。
这些都是非常正当的理由,但是,在创建扩展之前,你需要首先明白扩展是什么?
扩展是什么?
如果你用过PHP,那么你肯定用到过扩展。除了少数例外,每个用户空间的函数都被组织在不同的扩展中。这些函数中的很多够成了standard扩展-总数超过400。PHP本身带有86个扩展(原文写就之时-译注),平均每个含有大约30个函数。数学操作方面大约有2500个函数。似乎这还不够, PECL仓库另外提供了超过100个扩展,而且互联网上可以找到更多。
“除了扩展中的函数,还有什么?”我听到了你的疑问。 “扩展的里面是什么?PHP的‘核心’是什么?”
PHP的核心由两部分组成。最底层是Zend引擎(ZE)。ZE把人类易读的脚本解析成机器可读的符号,然后在进程空间内执行这些符号。ZE也处理内存管理、变量作用域及调度程序调用。另一部分是PHP内核,它绑定了SAPI层(Server Application Programming Interface,通常涉及主机环境,如Apache,IIS,CLI,CGI等),并处理与它的通信。它同时对safe_mode和open_basedir的检测提供一致的控制层,就像流层将fopen()、fread()和fwrite()等用户空间的函数与文件和网络I/O联系起来一样。
生存周期
当给定的SAPI启动时,例如在对/usr/local/apache/bin/apachectl start的响应中,PHP由初始化其内核子系统开始。在接近启动例程的末尾,它加载每个扩展的代码并调用其模块初始化例程(MINIT)。这使得每个扩展可以初始化内部变量、分配资源、注册资源处理器,以及向ZE注册自己的函数,以便于脚本调用这其中的函数时候ZE知道执行哪些代码。
接下来,PHP等待SAPI层请求要处理的页面。对于CGI或CLI等SAPI,这将立刻发生且只发生一次。对于Apache、IIS或其他成熟的web服务器SAPI,每次远程用户请求页面时都将发生,因此重复很多次,也可能并发。不管请求如何产生,PHP开始于要求ZE建立脚本的运行环境,然后调用每个扩展的请求初始化 (RINIT)函数。RINIT使得扩展有机会设定特定的环境变量,根据请求分配资源,或者执行其他任务,如审核。 session扩展中有个RINIT作用的典型示例,如果启用了session.auto_start选项,RINIT将自动触发用户空间的session_start()函数以及预组装$_SESSION变量。
一旦请求被初始化了,ZE开始接管控制权,将PHP脚本翻译成符号,最终形成操作码并逐步运行之。如任一操作码需要调用扩展的函数,ZE将会把参数绑定到该函数,并且临时交出控制权直到函数运行结束。
脚本运行结束后,PHP调用每个扩展的请求关闭(RSHUTDOWN)函数以执行最后的清理工作(如将session变量存入磁盘)。接下来,ZE执行清理过程(垃圾收集)-有效地对之前的请求期间用到的每个变量执行unset()。
一旦完成,PHP继续等待SAPI的其他文档请求或者是关闭信号。对于CGI和CLI等SAPI,没有“下一个请求”,所以SAPI立刻开始关闭。关闭期间,PHP再次遍历每个扩展,调用其模块关闭(MSHUTDOWN)函数,并最终关闭自己的内核子系统。
这个过程乍听起来很让人气馁,但是一旦你深入一个运转的扩展,你会逐渐开始了解它。
内存分配
为了避免写的不好的扩展丢失内存,ZE使用附加的标志来执行自己内部的内存管理器以标识持久性。持久分配的内存意味着比单次请求更持久。对比之下,对于在请求期间的非持久分配,不论是否调用释放(内存)函数,都将在请求尾期被释放。例如,用户空间的变量被分配为非持久的,因为请求结束后它们就没用了。
然而,理论上,扩展可以依赖ZE在页面请求结束时自动释放非持久内存,但是不推荐这样做。因为分配的内存将在很长时间保持为未回收状态,与之相关联的资源可能得不到适当的关闭,并且吃饭不擦嘴是坏习惯。稍后你会发现,事实上确保所有分配的数据都被正确清理很容易。
让我们简单地比较传统的内存分配函数(只应当在外部库中使用)和PHP/ZE的持久的以及非持久的内存非配函数。
传统的 非持久的 持久的
malloc(count)
calloc(count, num)  emalloc(count)
ecalloc(count, num)  pemalloc(count, 1)*
pecalloc(count, num, 1) 
strdup(str)
strndup(str, len)  estrdup(str)
estrndup(str, len)  pestrdup(str, 1)
pemalloc() & memcpy() 
free(ptr) efree(ptr) pefree(ptr, 1)
realloc(ptr, newsize) erealloc(ptr, newsize) perealloc(ptr, newsize, 1)
malloc(count * num + extr)** safe_emalloc(count, num, extr) safe_pemalloc(count, num, extr)
* pemalloc()族包含一个‘持久’标志以允许它们实现对应的非持久函数的功能。
   例如:emalloc(1234)与pemalloc(1234, 0)相同。
** safe_emalloc()和(PHP 5中的)safe_pemalloc()执行附加检测以防整数溢出。

建立构建环境
既然你已经了解了一些PHP和Zend引擎的内部运行理论,我打赌你希望继续深入并开始构建一些东西。在此之前,你需要收集一些必需的构建工具并设定适合于你的目的的环境。
首先,你需要PHP本身及其所需要的构建工具集。如果你不熟悉从源码构建PHP,我建议你看看http://www.php.net/install.unix。(为Windows开发PHP扩展将在以后的文章中提到)。然而,使用PHP的二进制分发包有些冒险,这些版本倾向于忽略./configure的两个重要选项,它们在开发过程中很便利。第一个--enable-debug。这个选项将把附加的符号信息编译进PHP的执行文件,以便如果发生段错误,你能从中得到一个内核转储文件,使用gdb追踪并发现什么地方以及为什么会发生段错误。另一个选项依赖于你的PHP版本。在PHP 4.3中该选项名为--enable-experimental-zts,在PHP 5及以后的版本中为--enable-maintainer-zts。这个选项使PHP以为自己执行于多线程环境,并且使你能捕获通常的程序错误,然而它们在非多线程环境中是无害的,却使你的扩展不可安全用于多线程环境。一旦你已经使用这些额外的选项编译了PHP并安装于你的开发服务器(或者工作站)中,你就可以把你的第一个扩展加入其中了。
Hello World
什么程序设计的介绍可以完全忽略必需的Hello World程序?此例中,你将制作的扩展导出一个简单函数,它返回一个含有“Hello World”的字符串。用PHP的话你或许这样做:
function hello_world() {
return 'Hello World';
}
?>
 
现在你将把它转入PHP扩展。首先,我们在你的PHP源码树的目录ext/中创建一个名为hello的目录,并且chdir进入该目录。事实上,这个目录可以置于PHP源码树之中或之外的任何地方,但是我希望你把它放在这儿,以例示一个在以后的文章中出现的与此无关的概念。你需要在这儿创建3个文件:包含hello_world函数的源码文件,包含引用的头文件,PHP用它们加载你的扩展,以及phpize用来准备编译你的扩展的配置文件。
config.m4
PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello   Enable Hello World support])
if test "$PHP_HELLO" = "yes"; then
  AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
  PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi
 
php_hello.h
#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"
PHP_FUNCTION(hello_world);
extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry
#endif
 
hello.c
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_hello.h"
static function_entry hello_functions[] = {
PHP_FE(hello_world, NULL)
{NULL, NULL, NULL}
};
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
NULL,
NULL,
NULL,
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif
PHP_FUNCTION(hello_world)
{
RETURN_STRING("Hello World", 1);
}
 
在上面的示例扩展中,你所看到的代码大多是黏合剂,作为将扩展引入PHP的协议语言并且在其间建立会话用于通信。只有最后四行才是你所认为“实际做事的代码”,它们负责与用户空间的脚本交互这一层次。这些代码看起来确实非常像之前看到的PHP代码,而且一看就懂:
声明一个名为hello_world的函数
让该函数返回字符串:“Hello World”
....嗯....1? 那个1是怎么回事儿?
回忆一下,ZE包含一个复杂的内存管理层,它可以确保分配的资源在脚本退出时被释放。然而,在内存管理领域,两次释放同一块内存是绝对禁止的(big no-no)。这种被称为二次释放(double freeing)的做法,是引起段错误的一个常见因素,原因是它使调用程序试图访问不再拥有的内存。类似地,你不应该让ZE去释放一个静态字符串缓冲区(如我们的示例扩展中的“Hello World”),因为它存在于程序空间,而不是被任何进程(process)拥有的数据块。RETURN_STRING()可以假定传入其中的任何字符串都需要被复制以便稍后可被安全地释放;但是由于内部的函数给字符串动态地分配内存、填充并返回并不罕见,第二参数RETURN_STRING()允许我们指定是否需要拷贝字符串的副本。要进一步说明这个概念,下面的代码片段与上面的对应版本等效:
PHP_FUNCTION(hello_world)
{
char *str;
str = estrdup("Hello World");
RETURN_STRING(str, 0);
}
 
在这个版本中,你手工为最终将被传回调用脚本的字符串“Hello World”分配内存,然后把这快内存“给予”RETURN_STRING(),用第二参数0指出它不需要制作自己的副本,可以拥有我们的。
构建你的扩展
本练习的最后一步是将你的扩展构建为可动态加载的模块。如果你已经正确地拷贝了上面的代码,只需要在ext/hello/中运行3个命令:
$ phpize
$ ./configure --enable-hello
$ make
每个命令都运行后,可在目录ext/hello/modules/中看到文件hello.so。现在,你可像其他扩展一样把它拷贝到你的扩展目录(默认是/usr/local/lib/php/extensions/,检查你的php.ini以确认),把extension=hello.so加入你的php.ini以使PHP启动时加载它。 对于CGI/CLI,下次运行PHP就会生效;对于web服务器SAPI,如Apache,需要重新启动web服务器。我们现在从命令行尝试下:
$ php -r 'echo hello_world();'
如果一切正常,你会看到这个脚本输出的Hello World,因为你的已加载的扩展中的函数hello_world()返回这个字符串,而且echo命令原样输出传给它的内容(本例中是函数的结果)。
可以同样的方式返回其他标量,整数值用RETURN_LONG(),浮点值用 RETURN_DOUBLE(),true/false值用RETURN_BOOL(),RETURN_NULL()?你猜对了,是NULL。我们看下它们各自在实例中的应用,通过在文件hello.c中的function_entry结构中添加对应的几行PHP_FE(),并且在文件结尾添加一些PHP_FUNCTION()。
static function_entry hello_functions[] = {
PHP_FE(hello_world, NULL)
PHP_FE(hello_long, NULL)
PHP_FE(hello_double, NULL)
PHP_FE(hello_bool, NULL)
PHP_FE(hello_null, NULL)
{NULL, NULL, NULL}
};
PHP_FUNCTION(hello_long)
{
RETURN_LONG(42);
}
PHP_FUNCTION(hello_double)
{
RETURN_DOUBLE(3.1415926535);
}
PHP_FUNCTION(hello_bool)
{
RETURN_BOOL(1);
}
PHP_FUNCTION(hello_null)
{
RETURN_NULL();
}
 
你也需要在头文件php_hello.h中函数hello_world()的原型声明旁边加入这些函数的原型声明,以便构建进程正确进行:
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);
 
由于你没有改变文件config.m4,这次跳过phpize和./configure步骤直接跳到make在技术上是安全的。然而,此时我要你再次做完全部构建步骤,以确保构建良好。另外,你应该调用make clean all而不是简单地在最后一步make,确保所有源文件被重建。重复一遍,迄今为止,根据你所做得改变的类型这些(步骤)不是必需的,但是安全比混淆要好。一旦模块构建好了,再次把它拷贝到你的扩展目录,替换旧版本。
此时你可以再次调用PHP解释器, 简单地传入脚本测试刚加入的函数。事实上,为什么不现在就做呢?我会在这儿等待...
完成了?好的。如果用了var_dump()而不是echo查看每个函数的输出,你或许注意到了hello_bool()返回true。那就是RETURN_BOOL()中的值1表现的结果。和在PHP脚本中一样,整数值0等于FALSE,而其他整数等于TRUE。仅仅是作为约定,扩展作者通常用1,鼓励你也这么做,但是不要感觉被它限制了。出于另外的可读性目的,也可用宏RETURN_TRUE和RETURN_FALSE;再来一个hello_bool(),这次使用RETURN_TRUE:
PHP_FUNCTION(hello_bool)
{
RETURN_TRUE;
}
 
注意这儿没用括号。那样的话,与其他的宏RETURN_*()相比,RETURN_TRUE和RETURN_FALSE的样式有区别(are aberrations),所以确信不要被它误导了(to get caught by this one)。
大概你注意到了,上面的每个范例中,我们都没传入0或1以表明是否进行拷贝。这是因为,对于类似这些简单的小型标量,不需要分配或释放额外的内存(除了变量容器自身-我们将在第二部分作更深入的考查。)
还有其他的三种返回类型:资源(就像mysql_connect(),fsockopen()和ftp_connect()返回的值的名字一样,但是不限于此),数组(也被称为HASH)和对象(由关键字new返回)。当我们深入地讲解变量时,会在第二部分看到它们。
初始设置(INI)
Zend引擎提供了两种管理INI值的途径。现在我们来看简单一些的,然后当你处理全局数据时再探究更完善但也更复杂的方式。
假设你要在php.ini中为你的扩展定义一个值,hello.greeting,它保存将在hello_world()函数中用到的问候字符串。你需要向hello.c和php_hello.h中增加一些代码,同时对hello_module_entry结构作一些关键性的改变。先在文件php_hello.h中靠近用户空间函数的原型声明处增加如下原型:
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);
 
现在进入文件hello.c,去掉当前版本的hello_module_entry,用下面的列表替换它:
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
NULL,
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
PHP_INI_END()
PHP_MINIT_FUNCTION(hello)
{
REGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(hello)
{
UNREGISTER_INI_ENTRIES();
return SUCCESS;
}
 
现在,你只需要在文件hello.c顶部的那些#include旁边增加一个#include,这样可以获得正确的支持INI的头文件:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "php_hello.h"
 
最后,你可修改函数hello_world让它使用INI的值:
PHP_FUNCTION(hello_world)
{
RETURN_STRING(INI_STR("hello.greeting"), 1);
}
 
注意,你将要拷贝从INI_STR()返回的值。这是因为,在进入PHP变量堆栈之前(as far as the PHP variable stack is concerned),它都是个静态字符串。实际上,如果你试图修改这个返回的字符串,PHP执行环境会变得不稳定,甚至崩溃。
本节中的第一处改变引入了两个你非常熟悉的函数:MINIT和MSHUTDOWN。正如稍早提到的,这些方法在SAPI初始启动和最终关闭期间被各自调用。它们不会在请求期间和请求之间被调用。本例中它们用来将你的扩展中定义的条目向php.ini注册。本系列后面的教程中,你也将看到如何使用MINIT和MSHUTDOWN函数注册资源、对象和流处理器。
函数hello_world()中使用INI_STR()取得hello.greeting条目的当前字符串值。也存在其他类似函数用于取得其他类型的值,长整型、双精度浮点型和布尔型,如下面表格中所示;同时也提供另外的ORIG版本,它们提供在php.ini文件中的INI(的原始)设定(在被 .htaccess或ini_set()指令改变之前)(原文:provides the value of the referenced INI setting as it was set in php.ini-译注)。
当前值 原始值 类型
INI_STR(name) INI_ORIG_STR(name) char * (NULL terminated)
INI_INT(name) INI_ORIG_INT(name) signed long
INI_FLT(name) INI_ORIG_FLT(name) signed double
INI_BOOL(name) INI_ORIG_BOOL(name) zend_bool
传入PHP_INI_ENTRY()的第一个参数含有在php.ini文件中用到的名字字符串。为了避免命名空间冲突,你应该使用同函数一样的约定,即是,将你的扩展的名字作为所有值的前缀,就像你对hello.greeting做的一样。仅仅是作为约定,一个句点被用来分隔扩展的名字和更具说明性的初始设定名字。
第二个参数是初始值(默认值?-译注),而且,不管它是不是数字值,都要使用char*类型的字符串。这主要是依据如下事实:.ini文件中的原值就是文本-连同其他的一切作为一个文本文件存储。你在后面的脚本中所用到的INI_INT()、INI_FLT()或INI_BOOL()会进行类型转换。
传入的第三个值是访问模式修饰符。这是个位掩码字段,它决定该INI值在何时和何处是可修改的。对于其中的一些,如register_globals,它只是不允许在脚本中用ini_set()改变该值,因为这个设定只在请求启动期间(在脚本能够运行之前)有意义。其他的,如allow_url_fopen,是管理(员才可进行的)设定,你不会希望共享主机环境的用户去修改它,不论是通过ini_set()还是.htaccess的指令。该参数的典型值可能是PHP_INI_ALL,表明该值可在任何地方被修改。然后还有PHP_INI_SYSTEM|PHP_INI_PERDIR,表明该设定可在php.ini文件中修改,或者通过.htaccess文件中的Apache指令修改,但是不能用ini_set()修改。或者,也可用PHP_INI_SYSTEM,表示该值只能在php.ini文件中修改,而不是任何其他地方。
我们现在忽略第四个参数,只是提一下,它允许在初始设定发生改变时-例如使用ini_set()-触发一个方法回调。这使得当设定改变时,扩展可以执行更精确的控制,或是根据新的设定触发一个相关的行为。
全局数值
扩展经常需要在一个特定的请求中由始至终跟踪一个值,而且要把它与可能同时发生的其他请求分开。在非多线程的SAPI中很简单:只是在源文件中声明一个全局变量并在需要时访问它。问题是,由于PHP被设计为可在多线程web服务器(如Apache 2和IIS)中运行,它需要保持各线程使用的全局数值的独立。通过使用TSRM (Thread Safe Resource Management,线程安全的资源管理器) 抽象层-有时称为ZTS(Zend Thread Safety,Zend线程安全),PHP将其极大地简化了。实际上,此时你已经用到了部分TSRM,只是没有意识到。(不要探寻的太辛苦;随着本系列的进行,你将到处看到它的身影。)
如同任意的全局作用域,创建一个线程安全的作用域的第一步是声明它。鉴于本例的目标,你将会声明一个值为0的long型全局数值。每次hello_long()被调用,都将该值增1并返回。在php_hello.h文件中的#define PHP_HELLO_H语句后面加入下面的代码段:
#ifdef ZTS
#include "TSRM.h"
#endif
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
ZEND_END_MODULE_GLOBALS(hello)
#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif
 
这次也会使用RINIT方法,所以你需要在头文件中声明它的原型:
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);
 
现在我们回到文件hello.c中并紧接着包含代码块后面加入下面的代码:
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "php_hello.h"
ZEND_DECLARE_MODULE_GLOBALS(hello)
 
改变hello_module_entry,加入PHP_RINIT(hello):
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
PHP_RINIT(hello),
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
 
而且修改你的MINIT函数,附带着另外两个函数,它们在请求启动时执行初始化:
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
}
PHP_RINIT_FUNCTION(hello)
{
HELLO_G(counter) = 0;
return SUCCESS;
}
PHP_MINIT_FUNCTION(hello)
{
ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);
REGISTER_INI_ENTRIES();
return SUCCESS;
}
 
最后,你可修改hello_long()函数使用这个值:
PHP_FUNCTION(hello_long)
{
HELLO_G(counter)++;
RETURN_LONG(HELLO_G(counter));
}
 
在你加入php_hello.h的代码中,你用到了两个宏-ZEND_BEGIN_MODULE_GLOBALS()和ZEND_END_MODULE_GLOBALS()-用来创建一个名为zend_hello_globals的结构,它包含一个long型的变量。然后有条件地将HELLO_G()定义为从线程池中取得数值,或者从全局作用域中得到-如果你编译的目标是非多线程环境。
在hello.c中,你用ZEND_DECLARE_MODULE_GLOBALS()宏来例示zend_hello_globals结构,或者是真的全局(如果此次构建是非线程安全的),或者是本线程的资源池的一个成员。作为扩展作者,我们不需要担心它们的区别,因为Zend引擎为我们打理好这个事情。最后,你在 MINIT中用ZEND_INIT_MODULE_GLOBALS()分配一个线程安全的资源id-现在还不用考虑它是什么。
你可能已经注意到了,php_hello_init_globals()实际上什么也没做,却得多声明个RINIT将变量counter初始化为0。为什么?
关键在于这两个函数何时被调用。php_hello_init_globals()只在开始一个新的进程或线程时被调用;然而, 每个进程都能处理多个请求,所以用这个函数将变量counter初始化为0将只在第一个页面请求时运行。向同一进程发出的后续页面请求将仍会得到以前存储在这儿的counter变量的值,因此不会从0开始计数。要为每个页面请求把counter变量初始化为0,我们实现RINIT函数, 正如之前看到的,它在每个页面请求之前被调用。此时我们包含php_hello_init_globals()函数是因为稍后你将会用到它,而且在ZEND_INIT_MODULE_GLOBALS()中为这个初始化函数传入NULL将导致在非多线程的平台产生段错误。
初始设置(INI)作为全局数值
回想一下,一个用PHP_INI_ENTRY()声明的php.ini值会作为字符串被解析,并按需用INI_INT()、INI_FLT()和INI_BOOL()转为其他格式。对于某些设定,那么做使得在脚本的执行过程中,当读取这些值时反复做大量不需要的重复工作。幸运的是,可以让ZE将INI值存储为特定的数据类型,并只在它的值被改变时执行类型转换。我们通过声明另一个INI值来尝试下,这次是个布尔值,用来指示变量counter是递增还是递减。开始吧,先把php_hello.h中的MODULE_GLOBALS块改成下面的代码:
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)
 
接下来,修改PHP_INI_BEGIN()块,声明INI值,像这样:
PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()
 
现在用下面的代码初始化init_globals方法中的设定:
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
hello_globals->direction = 1;
}
 
并且最后,在hello_long()中应用这个初始设定来决定是递增还是递减:
PHP_FUNCTION(hello_long)
{
if (HELLO_G(direction)) {
HELLO_G(counter)++;
} else {
HELLO_G(counter)--;
}
RETURN_LONG(HELLO_G(counter));
}
 
就是这些。在INI_ENTRY部分指定的OnUpdateBool方法会自动地把php.ini、.htaccess或者脚本中通过ini_set()提供的值转换为适当的TRUE/FALSE值,这样你就可以在脚本中直接访问它们。STD_PHP_INI_ENTRY的最后三个参数告诉PHP去改变哪个全局变量,我们的扩展的全局(作用域)的结构是什么样子,以及持有这些变量的全局作用域的名字是什么。
核对(代码)完整性
迄今为止,我们的三个文件应该类似于下面的列表。(为了可读性,一些项目被移动和重新组织了。)
config.m4
PHP_ARG_ENABLE(hello, whether to enable Hello World support,
[ --enable-hello   Enable Hello World support])
if test "$PHP_HELLO" = "yes"; then
  AC_DEFINE(HAVE_HELLO, 1, [Whether you have Hello World])
  PHP_NEW_EXTENSION(hello, hello.c, $ext_shared)
fi
 
php_hello.h
#ifndef PHP_HELLO_H
#define PHP_HELLO_H 1
#ifdef ZTS
#include "TSRM.h"
#endif
ZEND_BEGIN_MODULE_GLOBALS(hello)
long counter;
zend_bool direction;
ZEND_END_MODULE_GLOBALS(hello)
#ifdef ZTS
#define HELLO_G(v) TSRMG(hello_globals_id, zend_hello_globals *, v)
#else
#define HELLO_G(v) (hello_globals.v)
#endif
#define PHP_HELLO_WORLD_VERSION "1.0"
#define PHP_HELLO_WORLD_EXTNAME "hello"
PHP_MINIT_FUNCTION(hello);
PHP_MSHUTDOWN_FUNCTION(hello);
PHP_RINIT_FUNCTION(hello);
PHP_FUNCTION(hello_world);
PHP_FUNCTION(hello_long);
PHP_FUNCTION(hello_double);
PHP_FUNCTION(hello_bool);
PHP_FUNCTION(hello_null);
extern zend_module_entry hello_module_entry;
#define phpext_hello_ptr &hello_module_entry
#endif
 
hello.c
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "php_hello.h"
ZEND_DECLARE_MODULE_GLOBALS(hello)
static function_entry hello_functions[] = {
PHP_FE(hello_world, NULL)
PHP_FE(hello_long, NULL)
PHP_FE(hello_double, NULL)
PHP_FE(hello_bool, NULL)
PHP_FE(hello_null, NULL)
{NULL, NULL, NULL}
};
zend_module_entry hello_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_HELLO_WORLD_EXTNAME,
hello_functions,
PHP_MINIT(hello),
PHP_MSHUTDOWN(hello),
PHP_RINIT(hello),
NULL,
NULL,
#if ZEND_MODULE_API_NO >= 20010901
PHP_HELLO_WORLD_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
#ifdef COMPILE_DL_HELLO
ZEND_GET_MODULE(hello)
#endif
PHP_INI_BEGIN()
PHP_INI_ENTRY("hello.greeting", "Hello World", PHP_INI_ALL, NULL)
STD_PHP_INI_ENTRY("hello.direction", "1", PHP_INI_ALL, OnUpdateBool, direction, zend_hello_globals, hello_globals)
PHP_INI_END()
static void php_hello_init_globals(zend_hello_globals *hello_globals)
{
hello_globals->direction = 1;
}
PHP_RINIT_FUNCTION(hello)
{
HELLO_G(counter) = 0;
return SUCCESS;
}
PHP_MINIT_FUNCTION(hello)
{
ZEND_INIT_MODULE_GLOBALS(hello, php_hello_init_globals, NULL);
REGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(hello)
{
UNREGISTER_INI_ENTRIES();
return SUCCESS;
}
PHP_FUNCTION(hello_world)
{
RETURN_STRING("Hello World", 1);
}
PHP_FUNCTION(hello_long)
{
if (HELLO_G(direction)) {
HELLO_G(counter)++;
} else {
HELLO_G(counter)--;
}
RETURN_LONG(HELLO_G(counter));
}
PHP_FUNCTION(hello_double)
{
RETURN_DOUBLE(3.1415926535);
}
PHP_FUNCTION(hello_bool)
{
RETURN_BOOL(1);
}
PHP_FUNCTION(hello_null)
{
RETURN_NULL();
}
 

下一步是什么?
本教程探究了一个简单PHP扩展的结构,包括导出函数、返回值、声明初始设置(INI)以及在(客户端)请求期间跟踪其内部状态。

上海松善实业有限公司

    上海松善实业有限公司是一家集多品牌销售于一体的电线电缆骨干企业,公司成立于2016年。 公司拥有国内各大品牌:起帆、远东、上上、江南、胜华等。     主要产品有:高低压电力电缆、橡套电缆、控制电缆、架空绝缘电缆、塑胶电缆、电子计算机电缆、通讯电缆、...