存档

‘PHP’ 分类的存档

phar函数介绍

2016年11月30日 没有评论

php官方关于phar手册的地址,不过由于这个模块比较相对比较冷僻,所以没有人翻译手册,这里针对一些常用的函数,对手册做一些简明的翻译。

public Phar::__construct ( string $fname [, int $flags [, string $alias ]] )

构造函数,用来构建Phar对象。

$fname是一个指向一个phar归档文件的路径或者要创建的归档文件存放的路径,注意这个文件的扩展名必须包含“.phar”。

$flags传入给父类RecursiveDirectoryIteratord的标志。

$alias在调用流功能时应引用此Phar归档的别名.

错误和异常:被调用2次时,抛BadMethodCallException异常,当$fname指定的归档文件打不开时抛UnexpectedValueException异常。

 

public array Phar::buildFromDirectory ( string $base_dir [, string $regex ] )

从指定目录中的文件构造phar归档,此方法需要 将 php.ini 中的 phar.readonly 设为 0 以适合 Phar 对象. 否则, 将抛出PharException.用目录中的内容填充phar归档文件,第2个可选参数是一个正则表达式,用来过滤内容,匹配正则表达式的包含到归档文件中,否则排除。如果想要控制的更精细,试试Phar::buildFromIterator()。

$base_dir指定要填充到归档文件内容的目录路径(相对路径和绝对路径均可)。

$regex是一个可选正则参数用来过滤目录内的文件,只有匹配正则表达式的文件路径会被填充到归档文件中。

返回将文件的内部路径映射到文件系统上文件的完整路径的关联数组。

 

public void Phar::addFile ( string $file [, string $localname ] )

将一个文件从文件系统添加到 phar 档案中,此方法需要 将 php.ini 中的 phar.readonly 设为 0 以适合 Phar 对象. 否则, 将抛出PharException。通过这个方法,任何文件或者 URL 可以被添加到 phar 档案中。如果第二个可选参数 localname 被设定, 那么文件则会以该参数为名称保存到档案中,此外,file 参数会被作为路径保存在档案中。 URLs 必须提交 localname 参数,否则会抛出异常。 这个方法与 ZipArchive::addFile() 类似.

$file需要添加到 phar 档案的文件在磁盘上的完全(绝对)或者相对路径。

$localname文件保存到档案时的路径。

没有返回值,失败时会抛出异常。

 

public bool Phar::setDefaultStub ([ string $index [, string $webindex ]] )

用来将归档文件的加载器或者引导脚本设置为默认的加载器(几个英文单词吃不准,后面是原文 Used to set the PHP loader or bootstrap stub of a Phar archive to the default loader)。其实这个函数是Phar::createDefaultStub() 和Phar::setStub()的一个封装。

$index是一个归档文件内部的相对路径,如果在cli模式(命令行)下运行phar文件时执行的脚本。

$webindex是一个归档文件内部的相对路径,如果通过web浏览器访问phar归档文件时执行的脚本。

成功返回true,失败返回false。

 

public void Phar::compressFiles ( int $compression )

压缩当前phar归档中的文件。

$compression压缩时必须为Phar::GZ或 Phar::BZ2 ,或者使用 Phar::NONE 移除已有的压缩.

 

public bool Phar::extractTo ( string $pathto [, string|array $files [, bool $overwrite = false ]] )

将归档文件中的内容解压到指定目录。解压归档文件到硬盘时,会保留他们在归档文件中的权限。可选参数允许你控制那些文件提取到硬盘上或者当它们存在时是否覆盖它们。第2个可选参数可以是字符串或者数组,表示提取文件或目录的名字。默认的这个函数不会覆盖存在的文件,不过可以通过将第3个参数设置为true启用强制覆盖存在的文件。

$pathto解压提取到硬盘的路径。

$files可选参数指定提取归档文件或目录的名字,或者提取文件或目录的名字的数组。

$overwrite设置为true启用强制覆盖。

成功返回true,不过更好的办法是检查异常,如果没有异常抛出就是成功。

 

final public static bool Phar::loadPhar ( string $filename [, string $alias ] )

使用别名加载任意的phar归档文件。一般用来从外部来读取归档文件中的内容。最常用的就是给归档文件一个别名,随后就可以使用别名来引用归档文件,或者用于加载仅包含数据且不用于在PHP脚本中执行/包含的Phar存档。

$filename要打开的归档文件的相对或者绝对路径。

$alias设置一个用来引用归档文件的别名。许多phar档案在phar档案中指定一个显式别名,如果别名已经被制定过时,会抛出一个PharException异常。

成功时返回 TRUE, 或者在失败时返回 FALSE。

phar简介

2016年11月30日 没有评论

php5.3以后有一个非常不错的特性phar,有些类似java中的jar包,就是可以把php应用程序打包到一个单独的phar归档文件中。其实这个特性以前很早貌似就用,只不过最早是用php写的,所以性能上面不是特别好,后面官方用c扩展重新写一遍,文档可以查看Phar文档,不过目前90%的内容是英文的,下面我对介绍进行了一点简单翻译,当然英语水平一般,错误之处欢迎指正啊。

Phar简介

Phar扩展提供了一种将整个php应用放入一个单独文件被称为”phar”的归档,包更容易的发布和安装的解决方案。除了提供这种服务,phar扩展也提供了一种抽象化的文件格式的方法来创建和管理tar或者zip文件通过PharData类,很像Pdo提供了一种统一的接口来访问不同的数据库。和PDO不同之处,Pdo不能将数据在不同的数据库之间转换,Phar可以通过一行代码就可以在tar,zip和phar之间转换文件格式。查看Phar::convertToExecutable()示例。

phar是什么?phar归档文件最显著的特征是组合很多文件文件到一个单独的文件中。就像这样,一个phar文档提供了一种方式来发布一个完成的php应用程序在一个单独的文件中并且可以通过这个文件来运行它而无需解压到硬盘上。此外,phar归档文件也可以像其他php文件一样去被PHP执行,同时可以通过命令行和一台web服务器。Phar就像一种为PHP应用程序设计的拇指驱动器(U盘)。

Phar实现这些功能通过Stream Wrapper。一般来说,要使用一个在Phar归档中的php脚本文件,你可以用include。

函数介绍

可以参考另一篇关于phar函数手册的博客。

简单案例

这是一个比较简单的demo,希望通过这个能对phar有个比较初步的了解。

目录树如下:

|____build.php
|____config.json
|____extractPhar.php
|____lib
| |____class
| | |____empty.txt
| |____func.php
| |____stub.php
|____phar.phar
|____test_phar.php

build.php为生成phar归档文件的php脚本,config.json文件其实没什么用,extractPhar.php为解压phar到指定目录的脚本,lib目录下面就是测试用的库函数,phar.phar是用build.php生成的归档文件,test_phar.php是测试phar归档文件的脚本。下面我们一个一个的介绍这里面涉及到的脚本文件。

[build.php]

</pre>
<pre><?php</pre>
<pre>try{
    $phar = new Phar('phar.phar',0,'my.phar');
    $phar->buildFromDirectory(__DIR__.'/lib/');
    //$phar->buildFromDirectory(__DIR__.'/lib/',"/\.php/");
    $phar->addFile("config.json","config");
    $phar->setDefaultStub('stub.php','stub.php');
    $phar->compressFiles(Phar::GZ);
}catch (Exception $e){
    echo $e->getMessage();
}</pre>
<pre>

这里需要注意把php.ini 中的 phar.readonly 设为 0 以适合 Phar 对象. 否则, 将抛出PharException。

[func.php]

</pre>
<pre>function println($str){
    echo $str."\n";
}

function myFunc($input=__FILE__){
    println('Input File:'.$input);
    println('File Path:'.__DIR__);
}</pre>
<pre>

[stub.php]

</pre>
<pre><?php

require_once 'func.php';
myFunc(__FILE__);</pre>
<pre>

[test_phar.php]

</pre>
<pre><?php
include_once 'phar.phar';
include_once 'phar://my.phar/func.php';
myFunc(__FILE__);
$content = file_get_contents("phar://my.phar/config");
println($content);</pre>
<pre>

[extractPhar.php]

</pre>
<pre><?php
$phar = new Phar('phar.phar');
$phar->extractTo('./extract/');</pre>
<pre>

通过命令行脚本执行”php build.php”可以生成phar.phar归档文件。可以直接用”php phar.phar”执行phar归档文件,
它会执行”setDefaultStub”设置stub文件。extractPhar.php会将phar.phar归档文件中的内容解压到extract目录下面,
方面我们查看phar归档中的内容。
test_phar.php的执行结果如下:

Input File:phar:///Users/king/www/test/Phar/phar.phar/stub.php
File Path:phar:///Users/king/www/test/Phar/phar.phar
Input File:/Users/king/www/test/Phar/test_phar.php
File Path:phar:///Users/king/www/test/Phar/phar.phar
{
 "name" : "phar test project",
 "path" : "~/www/test/Phar"
}
extractPhar.php解压的extract目录如下:
|____class
| |____empty.txt
|____config
|____func.php
|____stub.php

php5.5新特性::class

2016年9月26日 没有评论

最近在了解laravel框架,发现它里面很多地方都使用了”类名::class”,这是php5.5以后新增的特性,功能有些类似get_class,不过get_class传入的是对象。

下面是官方手册中的描述和例子:

自 PHP 5.5 起,关键词 class 也可用于类名的解析。使用 ClassName::class 你可以获取一个字符串,包含了类 ClassName 的完全限定名称。这对使用了 命名空间 的类尤其有用。


<?php
namespace NS {
class ClassName {
}
echo ClassName::class;
}
?>

以上例程会输出:NS\ClassName.

我们可以拿它和get_class做一个比较:


<?php
NameSpace Test;

class Test{
}
var_dump(Test::class);
$t = new Test;
var_dump(get_class($t));

执行以后输出:

string(9) “Test\Test”
string(9) “Test\Test”

分类: PHP 标签:

KMP字符串比较算法(php版本)

2016年7月5日 没有评论

前段时间面试的时候,被一个公司的人问到过,今天正好在地铁上面看到了关于KMP算法的文章,作者写的很好,我这个算法渣都看懂了!于是就抽空写了这个PHP版本的kmp算法实现,写的不好的地方欢迎指正。

<?php
/**
 * kmp字符串比较算法
 */

/**
 * 获得所有前缀集合
 * @param string $string
 *
 * @return array
 */
function get_prefix_set($string){
    $len = strlen($string);
    if($len <= 1 ) return [];
    $prefixs = [];
    for($i=1;$i<$len;$i++){
        $prefixs[] = substr($string,0,$i);
    }
    return $prefixs;
}

/**
 * 获得所有后缀集合
 * @param string $string
 *
 * @return array
 */
function get_suffix_set($string){
    $len = strlen($string);
    if($len<=1) return [];
    $suffixs = [];
    for($i=1;$i<$len;$i++){
        $suffixs[] = substr($string,$i);
    }
    return $suffixs;
}

/**
 * 查找字符串$str2是否在$str1中出现
 * @param string $str1
 * @param string $str2
 *
 * @return bool
 */
function my_strstr($str1,$str2){
    $prefixSet = get_prefix_set($str2);
    $suffixSet = get_suffix_set($str2);
    $intersection = array_intersect($prefixSet,$suffixSet);
    $matchValue = 0;
    if(!empty($intersection)){
        foreach($intersection as $tmpstr){
            $len = strlen($tmpstr);
            if($len>$matchValue) $matchValue = $len;
        }
    }
    $str1_len = strlen($str1);
    $str2_len = strlen($str2);
    $endPos = $str1_len-$str2_len;
    if($endPos<0){
        return false;
    }
    for($i=0;$i<=$endPos;){
        $moveStep = 1;
        for($n=0;$n<$str2_len;$n++){
            if($str1[$i+$n] != $str2[$n]){//字符串不匹配时,跳出本次循环
                $moveStep = $n-$matchValue;
                $moveStep = $moveStep > 0 ? $moveStep : 1;
                break 1;
            }else if($str1[$i+$n] == $str2[$n] && $n==$str2_len-1){
                return true;
            }
        }
        $i +=$moveStep;
    }
    return false;
}

$result = my_strstr("abcabfcde","bfcd");
var_dump($result);

如果对KMP算法不是特别了解可以看一下《字符串匹配的KMP算法》

ab测试swoole和ngixn+php-fpm对比

2015年10月29日 没有评论

最近由于公司项目需要,要将一些原来的业务提取分离出来做成服务的模式供调用,目前我们的解决方案是使用韩天峰大大的swoole框架来解决这个问题。swoole这个框架虽然不算是一个新的东西,但是目前使用的其实还真不太多,在上一家做游戏的公司,我接触到了swoole,但是一致没有机会实用,所以一直不知道这个东西到底有多犀利。公司有几位做php的同事,不过他们一方面公司的项目比较近,需要他们去做,另一方面以前我对swoole也有过简单的接触,实话是说挺喜欢这个东西的。又花了近一周的时间来了解swoole,今天用swoole写的一个短信发送的项目就基本完成了,就用ab做了一些swoole测试,同时做了一些nginx的测试。swoole卓越的性能让我惊呆了,发现php原来也可以做这么强大多工作,感觉php的春天来了!

说了这么多废话,看一下swoole和nginx的性能测试对比吧。我这里就只给出ngixn测试的脚本代码,就是一个简单的mysql插入,而swoole那边是swoole框架搭建的一个server,用来处理收到的各种请求多,里面包含http协议头部和body部分的解析,以及请求的验证,最终是短信记录和发送。不过由于发送短信要收费,所以测试时把发送短信功能去掉,只有入库纪录功能。

nginx测试脚本

<?php
$link = mysql_connect("127.0.0.1","root","") or die(mysql_error());
mysql_set_charset("utf8");
mysql_select_db("sql_test");
$sql = "INSERT INTO ab_test (`msg`,`record_time`) VALUE(\"".uniqid()."itest\ ",\"".date("Y-m-d H:i:s”).”\”)”;
$result = mysql_query($sql);
var_dump($result);

swoole部分的代码由于设计公司业务和也比较多就不上了,只贴出nginx和swoole pk的测试数据了。

测试环境

os:centos7(mac pro的虚拟机里面装的centos7)

mem:4G(虚拟设置的4G,free查看时发现不到4G)

disk:固态硬盘(其实这里的影响最大是mysql的写入)

nginx 环境下就是上面的代码,但是swoole 是验证请求以后直接返回,由于我们swoole里面mysql和http请求都是异步处理,所以返回速度要比阻塞的nginx+fpm效率要高很多(nginx并不阻塞,关键是fpm和mysql).

10000请求1000并发

nginx的表现

nginx_10000_1000

swoole的表现

swoole_10000_1000

10000请求1500的并发

nginx的表现

nginx_10000_1500

swoole的表现

swoole_10000_1500

测试数据不难看出nginx+php-fpm模式在1000并发的时候就有数百多失败请求你,而且请求时间 用了75秒,而swoole只有12秒。我们再看一下qps参数(Requests per second)参数,由于1500的时候nginx此时全部返回500已经不可用,那就拿1000并发来看,nginx的在100多,swoole的达到了800多,而swoole在1500并发时候QPS也是800+。1500并发的时候虽然用时都差不多,但是nginx这使用失败率超过97%了,这个失败nginx几乎不可用,而swoole还是出色完成了任务。你可能会说swoole是直接返回,异步处理中可能也有失败的吧?很遗憾,我对比了数据表纪录,swool以100%的完成率全部完成了任务。

当然就目前来看1500还不是swoole的瓶颈,我在做2000并发测试时ab工具出了一些问题就没有再做。如果以后有机会就把它补上!

swoole,phper的春天!

分类: PHP 标签: ,

PHP扩展开发之扩展函数

2015年9月22日 没有评论

本来想着看了前面的文章,大家对php扩展开发中写自己的扩展函数会有一个很清晰的了解.但后来自己又梳理了一番,发现有几个地方可能还是需要展开一下的,这样会对扩展函数的开发有一个更深层次的理解.首先我们先来写一个简单的求和的php扩展函数.


PHP_FUNCTION(king_sum){
 long num1,num2,sum;
 if(zend_parse_parameter(ZEND_NUM_ARGS() TSRMLS_CC,"ll",&num1,&num2) == FAILURE){
 RETURN_FALSE();
 }
 sum = num1+num2;
 RETURN_LONG(sum);
}

代码很简单,接受2个参数,然后求和返回结果.接下来我们看看这里面都用到那些知识点.

PHP_FUNCTION宏

虽然上一篇在GDB里面已经展开说过这个宏了,这里还是要再展开一下,多了一些东西.


//main/php.h
#define PHP_FUNCTION ZEND_FUNCTION

//Zend/zend_API.h
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION( ZEND_FN(name) )
#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)

//Zend/zend.h
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

上面PHP_FUNCTION(king_sum)最终的展开结果是”void zif_king_sum(int ht, zval *return_value, zval **return_value_ptr,zval *this_ptr, int return_value_used TSRMLS_DC)”,除了函数名没有其他的变化(如果你对宏不是很清楚,可以google一下C语言宏).宏展开的过程这里面就不再多说了,今天注意一下宏”INTERNAL_FUNCTION_PARAMETERS”,里面几个参数是什么意思哪?我们来看一下官方的文档.

INTERNAL_FUNCTION_PARAMETERS
名称和类型 描述 访问宏
int ht 用户实际传递参数的数量 ZEND_NUM_ARGS()
zval *return_value PHP 变量的指针,可填充返回值传递给用户。默认值是 IS_NULL RETVAL_*, RETURN_*
zval **return_value_ptr 当返回引用时,PHP 将其设为变量的指针。不建议返回引用。
zval *this_ptr 假如这是一个方法调用,其指向存放 $this对象的 PHP 变量。 getThis()
int return_value_used 指示返回值是否会被调用者使用的标志。

这几个参数的作用上面已经说的很清楚了.ht这个参数我们在解析参数时会遇到,前面的几篇文章里面有传参时,我们都会看到ZEND_NUM_ARGS()宏,return_value参数是我们目前写扩展函数中要经常使用到的,下面会在函数返回值时候提到操作相关函数的宏.this_ptr是调用类的时候才会用到的,再开始了解PHP面向对象相关的知识以前,我们是很少会遇到它的.return_value_used是内核传递进来的一个参数,脚本里面调用函数的时候返回值是否使用.

从PHP_FUNCTION这个宏可以看出,扩展函数展开以后都是void类型的,而且函数声明中我们也就看到一个PHP函数传参相关的,其余都是和返回相关的。由于前面用了一整篇来写扩展函数的传参,所以这里我们就不在展开,没有看到的朋友可以回头看一下php扩展开发之传参.

变量修改

在最初的 文章php弱类型的实现中,我们就已经知道了php中的变量最终是通过zval来存储的,相信通过这几篇文章中的例子,大家也可以看出来一下。php在创建变量的时候需要申请内存空间和一些初始化。在申请空间的时候,php提供了一套自己的函数,通过“emalloc,ecalloc,erealloc,efree”这些函数申请的内存,php会进行自己的垃圾回收,有php自身的一套垃圾回收机制.就算你忘记efree了,在请求接受以后这部分内存也会被释放掉.但是你需要注意的是如果你的php-fpm是以静态模式运行的时候,php模块初始化部分的内存是不会释放掉的,这对高并发的网站来说有效的减少了php内存的申请和释放的次数,大大的提高了php的运行效率。但是如果你的内存只有1G而且跑的还有其他运行程序,那么你以这种方式来跑php可能是灾难性的,很容易就把系统的内存资源耗尽。同时需要注意的有使用emalloc申请的内存要用efree去释放,malloc申请的内存要用free去释放。因为efree释放的内存情况上面它是把占用的内存交还给了php的内存管理,方便其他系统调用。关于php的内存管理这里就不在展开,以后如果有机会,会再详细的谈一谈。

zval的内存申请和初始化时,php也提供了几个相关的宏来方便我们操作:


//Zend/zend.h
#define MAKE_STD_ZVAL(zv) \
 ALLOC_ZVAL(zv); \
 INIT_PZVAL(zv);

#define ALLOC_INIT_ZVAL(zp) \
 ALLOC_ZVAL(zp); \
 INIT_ZVAL(*zp);

#define INIT_PZVAL(z) \
 (z)->refcount__gc = 1; \
 (z)->is_ref__gc = 0;

#define INIT_ZVAL(z) z = zval_used_for_init;

//Zend/zend_gc.h
#define ALLOC_ZVAL(z) \
 do { \
 (z) = (zval*)emalloc(sizeof(zval_gc_info)); \
 GC_ZVAL_INIT(z); \
 } while (0)

用这些php提供的宏我们可以很容易的创建出一个变量,那么怎么去给这些变量赋值那?

zval要处理所有php变量,其最终存储字符串/数字/对象/数组等数据是靠的zvalue_value联合体。


typedef union _zvalue_value {
 long lval; /* long value */
 double dval; /* double value */
 struct {
 char *val;
 int len;
 } str;
 HashTable *ht; /* hash table value */
 zend_object_value obj;
 zend_ast *ast;
} zvalue_value;

如果我们自己直接对这个联合体进行赋值操作,那么我们需要根据真实情况进行复杂的操作,这是让人很头疼的问题。不过Zend内核也为我们提供了一系列的宏来解决这个问题。


#define ZVAL_RESOURCE(z, l) do { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = l; \
 Z_TYPE_P(__z) = IS_RESOURCE;\
 } while (0)

#define ZVAL_BOOL(z, b) do { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = ((b) != 0); \
 Z_TYPE_P(__z) = IS_BOOL; \
 } while (0)

#define ZVAL_NULL(z) { \
 Z_TYPE_P(z) = IS_NULL; \
 }

#define ZVAL_LONG(z, l) { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = l; \
 Z_TYPE_P(__z) = IS_LONG; \
 }

#define ZVAL_DOUBLE(z, d) { \
 zval *__z = (z); \
 Z_DVAL_P(__z) = d; \
 Z_TYPE_P(__z) = IS_DOUBLE; \
 }

#define ZVAL_STRING(z, s, duplicate) do { \
 const char *__s=(s); \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = strlen(__s); \
 Z_STRVAL_P(__z) = (duplicate?estrndup(__s, Z_STRLEN_P(__z)):(char*)__s);\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_STRINGL(z, s, l, duplicate) do { \
 const char *__s=(s); int __l=l; \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = __l; \
 Z_STRVAL_P(__z) = (duplicate?estrndup(__s, __l):(char*)__s);\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_EMPTY_STRING(z) do { \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = 0; \
 Z_STRVAL_P(__z) = STR_EMPTY_ALLOC();\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_ZVAL(z, zv, copy, dtor) do { \
 zval *__z = (z); \
 zval *__zv = (zv); \
 ZVAL_COPY_VALUE(__z, __zv); \
 if (copy) { \
 zval_copy_ctor(__z); \
 } \
 if (dtor) { \
 if (!copy) { \
 ZVAL_NULL(__zv); \
 } \
 zval_ptr_dtor(&__zv); \
 } \
 } while (0)

宏函数虽然不少,但是其命名很规范,都是ZVAL_*的格式,很容易记忆的,都是对应相应的数据类型。我们来看一下用这些宏创建变量和赋值的例子吧。


zval *name,*age;

MAKE_STD_ZVAL(name);

MAKE_STD_ZVAL(age);

ZVAL_STRING(name,"King",1);

ZVAL_LONG(age,25);

函数调用

我们写php代码的时候,经常要调用自己写的其他函数.同理如果其他的我们需要调用其他的扩展函数时,我们没必要再把扩展函数代码赋值一份过来调用,Zend内核提供了相应的方法来让我们调用其他扩展函数。但是今天这个部分就先跳过去,后面我会在花费一个整篇文章来介绍这个话题。

函数体中都是C/C++代码,这也没什么好说的。如果你对这些知识欠缺,自己找些C/C++的书籍补一补。

函数返回

从上面PHP_FUNCTION宏展开中,我们可以看到扩展函数被声明成void,是没有返回内容的,但是扩展里面传递进来了2个指针,return_value和return_value_ptr.这不像我们写PHP函数,一般函数结果都是直接返回的,而扩展函数毕竟是要由C/C++开发出来,所以难免会类似C/C++的风格,熟悉C/C++编程的朋友对这些肯定不会陌生。这是Zend内核设计的模式,我们做为开发者也没有办法去更改他,只能适应这个模式。

在Zend/zend_API.h中,我们可以看到一些操作return_value相关的宏RETVAL_*和RETURN_*,其实RETURN_*是对RETVAL_*的封装.


#define RETVAL_RESOURCE(l) ZVAL_RESOURCE(return_value, l)
#define RETVAL_BOOL(b) ZVAL_BOOL(return_value, b)
#define RETVAL_NULL() ZVAL_NULL(return_value)
#define RETVAL_LONG(l) ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d) ZVAL_DOUBLE(return_value, d)
#define RETVAL_STRING(s, duplicate) ZVAL_STRING(return_value, s, duplicate)
#define RETVAL_STRINGL(s, l, duplicate) ZVAL_STRINGL(return_value, s, l, duplicate)
#define RETVAL_EMPTY_STRING() ZVAL_EMPTY_STRING(return_value)
#define RETVAL_ZVAL(zv, copy, dtor) ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE ZVAL_BOOL(return_value, 0)
#define RETVAL_TRUE ZVAL_BOOL(return_value, 1)

#define RETURN_RESOURCE(l) { RETVAL_RESOURCE(l); return; }
#define RETURN_BOOL(b) { RETVAL_BOOL(b); return; }
#define RETURN_NULL() { RETVAL_NULL(); return;}
#define RETURN_LONG(l) { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; }
#define RETURN_STRING(s, duplicate) { RETVAL_STRING(s, duplicate); return; }
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; }
#define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE { RETVAL_FALSE; return; }
#define RETURN_TRUE { RETVAL_TRUE; return; }

RETURN_*宏是对RETVAL_*宏的二次封装,而RETVAL_*宏是对return_value支持的操作,而return_value指针前面我们已经提过它是我们返回结果的重要手段之一.

模块载入函数

当然如果你这写到这里就以为结束了,然后去动手编译你的扩展,你会看到提示你这个方法不存在的。那是因为C扩展和PHP语言之间有一个zend_module_entry,里面有个zend_function_entry数组,这里面是记录我们当前扩展中有那些php函数的。如果你是手动创建的话需要自己来填写这个模块信息的变量,zend_module_entry内容很多,我们可以按照ext_skel工具生成的模板来填写。

zend_module_entry king_module_entry = {
 STANDARD_MODULE_HEADER, //标准的一个结构体头部
 "king",//模块名称
 king_functions,//zend_function_entry数组,保存扩展php函数的数组
 PHP_MINIT(king),//模块初始化函数,没有具体的需要做的可以为NULL
 PHP_MSHUTDOWN(king),//模块关闭调用函数,没有具体的需要做的可以为NULL
 PHP_RINIT(king), //模块运行前调用函数,跟在PHP_MINIT函数之后,
 PHP_RSHUTDOWN(king), /* Replace with NULL if there's nothing to do at request end, */
 PHP_MINFO(king),//
 PHP_KING_VERSION,//这个地方是我们扩展的版本,版本信息
 STANDARD_MODULE_PROPERTIES
};

const zend_function_entry king_functions[] = {
 PHP_FE(confirm_king_compiled, NULL) /* For testing, remove later. */
 PHP_FE(king_sum,king_sum_arginfo)//添加我们自己的函数,前面是调用函数,后面是参数信息
 PHP_FE_END /* Must be the last line in king_functions[] */
};

//king_sum函数参数信息
ZEND_BEGIN_ARG_INFO(king_sum_arginfo,1)
 ZEND_ARG_INFO(0,king_sum_num1)
 ZEND_ARG_INFO(0,king_sum_num2)
ZEND_END_ARG_INFO()

//
typedef struct _zend_arg_info {
 const char *name;
 zend_uint name_len;
 const char *class_name;
 zend_uint class_name_len;
 zend_uchar type_hint;
 zend_uchar pass_by_reference;
 zend_bool allow_null;
 zend_bool is_variadic;
} zend_arg_info;

//位置main/php.h
#define PHP_FE ZEND_FE
//位置Zend/zebd_API.h
#define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeof(struct _zend_arg_info)-1), flags },

上面的代码中我们初始化了一个名为king_module_entry的zend_module_entry结构体,里面第三行的参数为当前模块函数数组。king_functions是一个zend_function_entry数组,我们可以使用PHP_FE宏往数组里面添加函数,第一个参数为函数名字,第二个参数为参数信息.参数信息是一个zend_arg_info数组:


typedef struct _zend_arg_info {
const char *name; //参数名称
zend_uint name_len;
const char *class_name;
zend_uint class_name_len;
zend_uchar type_hint;
zend_uchar pass_by_reference; //是否引用传递
zend_bool allow_null;
zend_bool is_variadic;
} zend_arg_info;

同样php也提供了一些宏来填充这些数组,下面我们来看一下这些宏:


#define ZEND_ARG_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, IS_OBJECT, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, sizeof(#name)-1, NULL, 0, IS_ARRAY, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, sizeof(#name)-1, NULL, 0, type_hint, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 1 },

#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \
static const zend_arg_info name[] = { \
{ NULL, 0, NULL, required_num_args, 0, return_reference, 0, 0 },
#define ZEND_BEGIN_ARG_INFO(name, _unused) \
ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO() };

ZEND_BEGIN_ARG_INFO是一个通用的结构体头部宏,要和ZEND_END_ARG_INFO配套使用.ZEND_ARG_INFO提供了否引用和参数名称的设置,pass_by_ref为是否引用,0为否,1为是,后面的name 为参数名称。第二个ZEND_ARG_PASS_INFO提供对参数是否引用的设置,参数具体值同上.后面的宏就不仔细介绍了,可以根据其名称推断出来其作用。

完成了这些,我们才算完成了一个完整的PHP扩展。我们可以按照前面文章进行编译和安装,然后进行测试了.

分类: PHP 标签: ,

php扩展开发之GDB调试

2015年8月19日 没有评论

俗话说的好"磨刀不误砍柴工",我们在开发扩展的时候,会像写C语言代码一样,经常会遇到程序core掉了.然而PHP脚本中常用的调试方式"echo,print_r,var_dump"却不能给我们什么大的帮助.今天我们就看看如何利用GDB来调试我们的php扩展程序?

为了方便我们使用gdb调试,最好在编译php程序的时候,把configure工具的”–enable-debug”参数启用,默认它是禁用的,因为它会产生很多方便我们调试的信息(注意在生产环境不要启用它,它会降低一部分php的性能).今天会主要说一下利用gdb进行断点调试,利用core文件调试已经不少的教程,所以我就不想再多做这些重复工作了.

前面的文章大家都看到如果声明一个php扩展函数,例如PHP_FUNCTION(count),这里如果我们直接利用cout函数来做断点,可能我们捕捉不到(这里我用了count做例子,但大家不要自己扩展里面这么做,因为count已经是php内建的扩展函数了,这样会引起不必要的问题).因为zend为了避免我们写的函数和C系统函数或这其他类库重名所以这里使用了PHP_FUNCTION宏进行声明,那么这个宏是什么样子哪?


#define PHP_FUNCTION ZEND_FUNCTION

#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION( ZEND_FN(name) )

#define ZEND_FN(name) zif_##name

#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)

上面我们提到的提到的”PHP_FUNCTION(count)“最终的展开结果是”void zif_count(int ht, zval *return_value, zval **return_value_ptr,zval *this_ptr, int return_value_used TSRMLS_DC)“,因此如果我们直接断点count可能就不会有结果的,这里我们需要用zif_count来断点就会捕捉到了.

如果我们使用的是一个第三方的扩展,我们不是很清楚里面有那些函数,或者修改过函数的前缀,已经不再是”zif”了.那么我们怎么获得我们需要的函数信息那?

我们可以使用nm工具来查看动态链接库里面的函数信息,就那我们上一篇那个交换2个数字的函数扩展为例子,看看如果使用nm吧.


nm king.so

//下面信息为nm摘取的部分信息

0000000000000e80 T zif_confirm_king_compiled
0000000000000f10 T zif_king_author
0000000000000fd0 T zif_king_call_system_func
0000000000000f30 T zif_king_exchange_number
0000000000000e60 T zm_activate_king
0000000000000e70 T zm_deactivate_king
0000000000001190 T zm_info_king
0000000000000e50 T zm_shutdown_king
0000000000000e40 T zm_startup_king

具体的信息如下图:

nm

 

看一下我们的扩展是否加入到php解释器中去那


php -m|grep king

//king

如果看到了”king”说明我们的扩展已经ok了,现在就做开始做gdb调试吧


cd /home/king/web_project/test/ext_king

gdb php

b zif_king_exchange_number

//Make breakpoint pending on future shared library load? (y or [n])

//这里用于这动态链接中,所以在我们php中是找不到这符号的,所以点y即可

//运行我们的php测试脚本

run ./exchange.php

//后面就是正常的gdb调试了

如此我们就可以清楚看到程序是怎么执行,更方便我们开发扩展程序.

如果我们有php程序core掉的文件,那么我们可以利用core文件进行调试.


gdb php -c core

#后面的可以和正常的gdb调试core文件一样,不过php提供了一个.gdbinit的帮助脚本,这个脚本在你下载的源文件的根目录下面,下面source的路径自行替换

source  ~/download/php-5.6.10/.gdbinit

zbacktrace

如果你需要更多.gdbinit脚本信息请自行google,或者去鸟哥的博客看看.

参考资料:

 如何调试PHP的Core之获取基本信息 

http://www.kuqin.com/language/20111022/313184.html

分类: PHP 标签:

php扩展开发之传参

2015年8月9日 没有评论

最近跟着服务器组同学学写C++,不是想转做C++开发,只是想借此机会锻炼一下自己.php的扩展不止可以使用原生的C,同样的可以使用C++来开发扩展来扩展我们的php.不过今天我们还不会谈C++扩展php,依旧还是原生的C来扩展我们的php.上一篇,我给了一个简单的扩展开发HelloWorld的例子.除了证明我们可以用C扩展php,它对我们日常工作可能要解决的问题来说毫无作用.今天我们来看一下php扩展传参是如果实现的,我们在日常的php开发当中写函数时要传参,同样我们看系统提供的函数中也有参数,但是我们在扩展函数的声明中是没有传参相关的参数的,那么它是如何实现的哪?

Zend API提供了解析参数的函数帮我们来解决这个问题:

ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, const char *type_spec, …)

ZEND_API int zend_get_parameters(int ht, int param_count, …)

我这里列了2个函数,zend_parse_parameters和zend_get_parameters,你可以在Zend/zend_API.c中找到他们.今天我们主要了解的是zend_parse_parameters,应为这是现阶段php5扩展中开发主要的解析参数的函数,而zend_get_parameters是针对php5之前的扩展用到的.需要你需要扩展老版本的php或者兼容旧的版本,请自行google翻阅资料,我个人觉得其意义不是很大,这里就不再多加探讨了.

zend_parse_parameters有2个主要参数,还有若干可变的参数,可变参数的作用就是接受我们php脚本中传递给扩展的参数.我们先看看这2个主要参数的作用吧:

int num_args就是本函数参数的个数,const char *type_spec是一个用于格式化的字符串,类似C里面的printf,不过他们2个却是完全不一样的.

参数   代表着的类型
b   Boolean
l   Integer 整型
d   Floating point 浮点型
s   String 字符串
r   Resource 资源
a   Array 数组
o   Object instance 对象
O   Object instance of a specified type 特定类型的对象
z   Non-specific zval 任意类型~
Z   zval**类型
f   表示函数、方法名称,PHP5.1里貌似木有... ...
下面的列表给出这些参数对应C里面的那些数据类型
参数  对应C里的数据类型
b   zend_bool
l   long
d   double
s   char*, int 前者接收指针,后者接收长度
r   zval*
a   zval*
o   zval*
O   zval*, zend_class_entry*
z   zval*
Z   zval**
后面跟的参数就是用来存储格式化字符串里面所表示的php传递的参数,需要注意的是字符串类型要占用2个参数,一个用来存储字符串,一个用来存储字符串长度.例子这里就不再单独的写了,细心的读者会发现上一篇扩展开发HelloWorld已经有介绍了.
今天我们写一个引用传参的例子,当初我在这个地方也纠结了好久,具体的是我们写一个互换2个数字的函数.
首先我们先用php写一下这个函数(注释部分为脚本输出):
<?php
$x = 1;
$y = 2;
exchange_num($x,$y);
echo $x."\n".$y."\n";
function exchange_num(&$a,&$b){
$c = $a;
$a = $b;
$b = $c;
}
//2
//1
这里我们使用了&符号来使用类似的功能,那么我们用扩展如何实现类似的引用哪?
PHP_FUNCTION(king_exchange_number){
long num1,num2;
zval *arg1,*arg2;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"zz", &arg1, &arg2) == FAILURE){
RETURN_NULL();
}
num1 = Z_LVAL_P(arg1);
num2 = Z_LVAL_P(arg2);
php_printf("num1:%ld,num2:%ld\n",num1,num2);
ZVAL_LONG(arg1,num2);
ZVAL_LONG(arg2,num1);
RETURN_NULL();
}
细心的朋友会发现我没有使用long类型来接受参数,而是换成zval *来接受.最初的时候我也试图利用long来接受数据,事实上我们可以用long来接受参数,我们可以读取参数里面的值,也可以改变参数的.但是我们却无法改变外部传进来参数的值,因为参数是局部变量,它在函数执行结束以后就会被释放掉,所以我们即使改变了这些用来接受参数的变量的值,其外部参数还是不会变化的.那么如果才能做到改变外部的参数哪?答案当然是指针,只有我们传递来的是一个指针的时候,我们才可以改变外部的参数值.但是我们回顾上面格式化字符串对照表里面,没有指针,但是注意看z是对应任意类型的,所以我们这里使用z.
在你当前的zend_function_entry数组中加入(以后的例子中会省略掉这一部分,读者自行添加即可)
PHP_FE(king_exchange_number,NULL)
然后保存重新编译扩展.然后我们在用我们写的扩展使用上面的功能(注释部分为脚本的输出结果).
<?php
$num1 = 11;
$num2 = 89;
king_exchange_number($num1,$num2);
echo "\$num1:$num1\n";
echo "\$num2:$num2\n";
//$num1:89
//$num2:11
到此相信你对php扩展传参也有个大致的了解了.文中的扩展在php-5.10中运行正常,如有疑问可以留言.

php扩展开发之”Hello World!”

2015年7月21日 没有评论

最近在看《PHP扩展开发及内核应用》,书的内容不是特别多,但感觉都是精华,值得大家去一读的.这个系列的文章也是自己的读书的笔记或者一点心得吧,会把自己遇到的问题和踩过的坑都会提出来,大家可以引以为鉴,文章的不足和错误也请大家指出来.PHP做为现在最火的脚本语言之一,在Web方面更是当之无愧的王者,吸引大家使用PHP的原因有很多,而PHP的社区也是非常活跃和成功的,目前PHP7也预计要在十月份出来正式版本的.

其实PHP的扩展也是十分简单和便捷的,当然你在考虑用C/C++扩展你的PHP之前,建议还是考虑一下这么做是必须的或者说是必要的吗(用C/C++扩展你的PHP这篇文章开头关于扩展PHP的动机还是建议大家看一下的)?

现在我们将进入今天的整体写一个简单的整体,开发我们自己的PHP扩展.我下载的PHP版本是PHP5.6.10,也是最新版的,需要注意的是这个系列文章中的PHP都是PHP5以后版本的,之前的都是版本Zend engine都是1而PHP5之后Zend engine升级到2,因此如果你要是还在维护PHP5之前的版本,那么这个系列文章对您的帮助可能不是很大.

PHP源码提供了一个非常便捷的帮助大家创建自己的扩展工具,在ext目录下面大家可以看到它–“ext_skel”,在windows下面替代的工具是”ext_skel_win32.php”.

我们来看一下ext_skel有那些参数


./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]]
 [--skel=dir] [--full-xml] [--no-help]

--extname=module       module is the name of your extension
 --proto=file          file contains prototypes of functions to create
 --stubs=file          generate only function stubs in file
 --xml                 generate xml documentation to be added to phpdoc-svn
 --skel=dir            path to the skeleton directory
 --full-xml            generate xml documentation for a self-contained extension(not yet implemented)
 --no-help             don't try to be nice and create comments in the code and helper functions to test if the module compiled

这里面的extname是指定我们自己扩展的名称,proto是指定包含我们想要创建的函数文件,一般文件是def格式的.

这里我们创建自己的hellworld.def文件,内容如下


void hello_world(string name)

下面我们只需要执行下面的命令,就完成了我们扩展的大部分工作了.


./ext_skel --extname=hello_world --proto=helloworld.def

Creating directory hello_world
Creating basic files: config.m4 config.w32 .gitignore hello_world.c php_hello_world.h CREDITS EXPERIMENTAL tests/001.phpt hello_world.php [done].

To use your new extension, you will have to execute the following steps:

1. $ cd ..
2. $ vi ext/hello_world/config.m4
3. $ ./buildconf
4. $ ./configure --[with|enable]-hello_world
5. $ make
6. $ ./sapi/cli/php -f ext/hello_world/hello_world.php
7. $ vi ext/hello_world/hello_world.c
8. $ make

Repeat steps 3-6 until you are satisfied with ext/hello_world/config.m4 and
step 6 confirms that your module is compiled into PHP. Then, start writing
code and repeat the last two steps as often as necessary.

上面的第1行是我们执行的命令,后面的脚本自动生成的内容,而我们开发扩展基本就按照这个流程来完成的.下面我们先去完成我们的hello_world函数去,然后我们的扩展工作基本就完成了.

进入ext/hello_world目录,编辑hello_world.c文件,这是我们扩展的主要文件了,只要我们的扩展内容不是特别多,我们可以完全把函数放在这里面的来完成.找到系统帮我们生成的代码中的”PHP_FUNCTION(hello_world)”,然后我们在此完成我们的hello_world功能.


PHP_FUNCTION(hello_world)
{
char *name = NULL;
int argc = ZEND_NUM_ARGS();
int name_len;

if (zend_parse_parameters(argc TSRMLS_CC, "s", &name, &name_len) == FAILURE)
return;

//php_error(E_WARNING, "hello_world: not yet implemented");
php_printf("Hello World!\n");
php_printf("Hello %s!\n",name);
RETURN_NULL();
}

其实我们就动了4行代码,注释掉php_error,然后和后面加上的3行代码.至此我们的功能就全部实现了.剩下就是按照扩展了.但是如果你去hello_world.c文件里面代码,你会发现ext_skel帮助我们生成很多代码,里面还是有一些东西是需要我们注意一下.

我们来看一下紧跟着hello_world函数的代码.


/* {{{ hello_world_functions[]
 *
 * Every user visible function must have an entry in hello_world_functions[].
 */
const zend_function_entry hello_world_functions[] = {
 PHP_FE(confirm_hello_world_compiled, NULL) /* For testing, remove later. */
 PHP_FE(hello_world, NULL)
 PHP_FE_END /* Must be the last line in hello_world_functions[] */
};
/* }}} */

/* {{{ hello_world_module_entry
 */
zend_module_entry hello_world_module_entry = {
 STANDARD_MODULE_HEADER,
 "hello_world",
 hello_world_functions,
 PHP_MINIT(hello_world),
 PHP_MSHUTDOWN(hello_world),
 PHP_RINIT(hello_world), /* Replace with NULL if there's nothing to do at request start */
 PHP_RSHUTDOWN(hello_world), /* Replace with NULL if there's nothing to do at request end */
 PHP_MINFO(hello_world),
 PHP_HELLO_WORLD_VERSION,
 STANDARD_MODULE_PROPERTIES
};
/* }}} */

“const zend_function_entry hello_world_functions”这个数组定义了我们扩展里面包含那些函数,当初我没有使用ext_skel的proto参数生成,就没有把我自己写的函数加入到hello_world_functions.导致我在编译安装完扩展以后,在PHP代码里面使用我在扩展里面写的函数的时候,就一直报错”Call to undefined function”,还是google好久才找到问题所在.所以如果你不是用ext_skel的proto参数生成的,在编译安装之前要注意一下这里.下面"zend_module_entry hello_world_module_entry "定义是hello_world模块.关于这些内容具体解释可以参考《PHP扩展开发与内核应用》第5章1节和4节的一些内容.

配置config.m4

现在如果我们要安装我们的扩展,还需要配置扩展目录下面的config.m4文件啦.

dnl PHP_ARG_WITH(hello_world, for hello_world support,
dnl Make sure that the comment is aligned:
dnl [ --with-hello_world Include hello_world support])

dnl Otherwise use enable:

PHP_ARG_ENABLE(hello_world, whether to enable hello_world support,
dnl Make sure that the comment is aligned:
[ --enable-hello_world Enable hello_world support])

dnl是m4文件的注释, 根据需要去掉相应的注释,PHP_ARG_WITH是我们在configure时”–with-xx”,此种方式一般依赖一些库的.而PHP_ARG_ENABLE是”–enable-xx”的方式,一般都是可以单独编译安装的,不依赖任何其他库支持的.我们这里选择PHP_ARG_ENABLE即可.

安装扩展

下面就是编译安装扩展了,如果你做过相关的工作就可以跳过这个小节的直接动手去尝试了.


phpize

./configure

make

sudo make install

在你的php.ini最后加上


extension=hello_world.so

//如果你的扩展是zend的,那需要改成zend_extension即可

重启你的apache或者php-fpm进行测试即可了.

测试扩展

编写一个测试的php脚本进行一下测试,查看我们的扩展是否安装成功.


<?php
hello_world("king");

?>

//最终输出如下,说明我们的扩展已经正常工作了

Hello World!
Hello king!

我们的hello world扩展开发也完成了.

 

php弱类型的实现

2015年6月30日 没有评论

php是一门弱类型的语言,这基本是为人所共知的.但是php是用C语言编写的,C语言是一门强类型的语言,而由C语言开发而来的php没有像C++/Java这些延续C语言的强类型,那php是如何实现这个哪?

PHP在声明和使用变量的时候,并不需要显示的指明其数据类型,这是php弱类型带来的便捷(当然也有性能的损耗),但这并不是说PHP中没有类型的.在PHP中,存在8种变量类型,我们大致可以分为3类

标量类:boolean、integer、float(double)

复合类型:array、object

特殊类型:resource、NULL

上面我们谈了PHP中的类型,只是为了方便我们清楚PHP也是有类型这个概念的.如果我们想要弄清楚上面的问题,那么我们就需要看看PHP是如何实现这些变量的定义和存储的.

变量存储结构

PHP把变量存储到zval结构体中,zval结构体定义在Zend/zend_types.h文件中


typedef struct _zval_struct zval;

不难看出zval实际上面是struct _zval_struct结构体,_zval_struct定义在Zend/zend.h


struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};

PHP使用这个结构体来存储所有用户变量.当然需要注意的是,PHP在存储变量时是存储在PHP用户空间(堆内存中),所以我们在PHP中可以把函数中的变量直接返回.而我们在C语言中有不少变量是声明在栈中的,如果我们直接把变量返回,那么可能会引发stack overflow的,特别是我们这边先接触PHP在回过头来看C的同学特别需要注意.PHP对堆内存有自己的内存管理和垃圾回收机制,所以一般我们PHP程序员大部分都是很少注意内存的消耗的.建议在程序比较占内存的时候,使用unset手动释放内存,这样会减轻服务器的资源压力.

zval结构体中有四个字段,其含义分别为:

属性名                含 义                默 认值
refcount__gc  表示引用计数         1
is_ref__gc        表示是否为引用       0
value                  存储变量的值
type                   变量具体的类型
目前对我们本文来说涉及到的是value和type,下面我们来看一下这2个字段.

变量类型

zval结构体的type字段就是我们今天问题的关键了,PHP就是通过存储时把字段的类型放在这个type字段里面来实现的弱类型的.type的值可以为: IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT和IS_RESOURCE 之一.从字面上就很好理解,他们只是类型的唯一标示,根据类型的不同将不同的值存储到value字段。 除此之外,和他们定义在一起的类型还有IS_CONSTANT和IS_CONSTANT_ARRAY。

变量的值存储

看前面在存储时,变量最终是存储在 zvalue_value联合体中的,其定义在Zend/zend.h头文件中.


typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
zend_ast *ast;
} zvalue_value;

这里使用联合体而不是用结构体是出于空间利用率的考虑,因为一个变量同时只能属于一种类型。 如果使用结构体的话将会不必要的浪费空间,而PHP中的所有逻辑都围绕变量来进
行的,这样的话, 内存浪费将是十分大的。这种做法成本小但收益非常大。

PHP正是通过上面几个结构体来实现的弱类型,如果您感觉还有写不清楚,可以看一下<深入理解PHP内核>这本书.