web应用会把数据持久化到磁盘,但是以何种方式持久化,有很深的学问。
刚开始学编程的时候,老师们叫我们把数据以文件的形式储存在磁盘中,这似乎是最简单的持久化方式,所有数据持久化的最终操作都是写磁盘文件。
写文件是最原始的数据持久化方式,也是效率最高的方式,但是有些逻辑因为数据组织过于复杂,需要关系型数据库来储存数据,近几年流行的非关系型数据库也是是一个不错的选择。

不管关系型数据库,还是非关系数据库,都会提供一个接口,用户无需关心数据到底存在磁盘的哪个位置,但是直接写文件不便于管理,于是开发了一个接口,提供了读,写,删的接口,用户无需关心数据存在哪里,只要提供桶名和文件id即可找到文件。

首先要下载一份php的源码,这里以php-5.5为准。

cd php/php-5.5.xx/ext
./ext_skel --extname=filebase
cd filebase
vi config.m4

修改其中的

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

把每行之前dnl删除掉。

接下来修改php-filebase.h中,删除以下一行

PHP_FUNCTION(confirm_filebase_compiled);	/* For testing, remove later. */

接下来修改filebase.c中,删除以下一部分

/* Remove the following function when you have successfully modified config.m4
   so that your module can be compiled into PHP, it exists only for testing
   purposes. */
……

https://github.com/gerpayt/php-filebase/commit/ff7512792903e0aa3fd3c76f71ad28c91fb6a8cd

接下来,增加第一个函数,叫做filebase_get,暂时就完成一个输出“hello world”的功能吧。

/* {{{ PHP_FUNCTION
*/
PHP_FUNCTION(filebase_get)
{
	php_printf("Hello World!\n");
}
ZEND_BEGIN_ARG_INFO(arginfo_filebase_get, 0)
ZEND_END_ARG_INFO()
/* }}} */

php_printf 就是c语言中的printf。

接下来,改进一点,不是直接输出这条信息,而是当做返回值交给php。

PHP_FUNCTION(filebase_get)
{
	char out[13];
	php_sprintf(out, "Hello World!\n");
	RETURN_STRING(out, 1);
}

用php_sprintf来生成一个字符串,使用RETURN_STRING来返回一个字符串。

接下来让函数读入一个参数,返回Hello xxx! 这样的字符串。

PHP_FUNCTION(filebase_get)
{
	char *name;
	int name_len;
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name, &name_len) == FAILURE)
	{
		return;
	}

	char out[10 + name_len];
	php_sprintf(out, "Hello %s!\n", name);
	RETURN_STRING(out, 1);
}

zend_parse_parameters这个函数用来读入参数

https://github.com/gerpayt/php-filebase/commit/6303011a4b7f498b14c986de99880d9632af3c48

让函数从ini读取参数,并打印这个参数

PHP_INI_BEGIN()
PHP_INI_END()

函数中间插入

    PHP_INI_ENTRY("filebase.root", "filebase", PHP_INI_ALL, NULL)

filebase.c中加入

const char *root = INI_STR("filebase.root");
const int root_len = strlen(root);

char out[10 + root_len];
php_sprintf(out, "Hello %s!\n", root);

https://github.com/gerpayt/php-filebase/commit/dabf00934477a471be783b70933c94d38636f0a0

从文件中读取内容
在filebase.c中加入

php_stream *stream;
char filename[root_len + bucket_len + path_len + 5 ];
char subpath[2];
subpath[0] = path[path_len - 2];
subpath[1] = path[path_len - 1];
subpath[2] = '\0';
 
php_sprintf(filename, "%s/%s/%s/%s", root, bucket, subpath, path);

int options = ENFORCE_SAFE_MODE | STREAM_MUST_SEEK;
	
stream = php_stream_open_wrapper(filename, "r", options, NULL);

if (!stream) {
	RETURN_FALSE;
}
int file_size;
php_stream_seek(stream, 0, SEEK_END);
file_size = php_stream_tell(stream);
php_stream_seek(stream, 0, SEEK_SET);
char buf[file_size+1];
php_stream_read(stream, buf, file_size);
buf[file_size] = '\0';
php_stream_free(stream, PHP_STREAM_FREE_CLOSE_PERSISTENT);

RETURN_STRING(buf, 1);

https://github.com/gerpayt/php-filebase/commit/77ca69a4a1913ceaf3c71fc0a67a8af4f7ab1e65

加入写入函数

PHP_FUNCTION(filebase_put)
{
	char *bucket, *path, *content;
	int bucket_len, path_len, content_len;
	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss", &bucket, &bucket_len, &path, &path_len, &content, &content_len) == FAILURE)
	{
		return;
	}

	const char *root = INI_STR("filebase.root");
	const int root_len = strlen(root);

	php_stream *stream, *stream_dir;
	char filename[root_len + bucket_len + path_len + 5 ];
	char dirname[root_len + bucket_len + path_len + 5 ];
	char subpath[2];
	subpath[0] = path[path_len - 2];
	subpath[1] = path[path_len - 1];
	subpath[2] = '\0';

	php_sprintf(filename, "%s/%s/%s/%s", root, bucket, subpath, path);
	php_sprintf(dirname, "%s/%s/%s", root, bucket, subpath);

	int options = ENFORCE_SAFE_MODE | STREAM_MUST_SEEK;

	stream_dir = php_stream_opendir(dirname, options, NULL);
	if (!stream_dir) {
		int res_mkdir;
		res_mkdir = php_stream_mkdir(dirname, 0755, options, NULL);
		if (!res_mkdir) {
			RETURN_FALSE;
		}
	}
	stream = php_stream_open_wrapper(filename, "w+", options, NULL);

	if (!stream) {
		RETURN_FALSE;
	}

	int file_size;
	file_size = php_stream_write(stream, content, content_len);
	php_stream_free(stream, PHP_STREAM_FREE_CLOSE_PERSISTENT);

	RETURN_LONG(file_size);
}

增加删除函数

PHP_FUNCTION(filebase_del)
{
	char *bucket, *path;
	int bucket_len, path_len;
	const char *root = INI_STR("filebase.root");
	const zend_bool debug = INI_BOOL("filebase.debug");
	const int root_len = strlen(root);
	int result;

	char *filename;
	char subpath[3];

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &bucket, &bucket_len, &path, &path_len) == FAILURE)
	{
		return;
	}

	filename = (char*)emalloc((root_len + bucket_len + path_len + 5)*sizeof(char));

	subpath[0] = path[path_len - 2];
	subpath[1] = path[path_len - 1];
	subpath[2] = '\0';

	php_sprintf(filename, "%s/%s/%s/%s", root, bucket, subpath, path);

	if(debug){
		php_printf("DEBUG: filename:%s\n", filename);
	}

	// TODO use php wrapper function
	result = unlink(filename);
	if (result)
	{
		RETURN_FALSE;
	}

	efree(filename);
	RETURN_TRUE;
}

差不多就这个三个函数,接下来是编译这个扩展,因为使用的是linux系统,编译相对容易一些。只需要以下几步

phpize
./configure
make
make install

windows下的编译是小羊帮我完成的,我这里就不叙述了。

编译好之后,会生成一个filebase.so的文件,在php.ini中引用这个扩展,并设置filebase.root的参数

[filebase]
extension=filebase.so
filebase.root="/home/filebase"

重启apache服务器,如果在phpinfo中找到相关信息,则表示filebase扩展安装成功。

尽情来玩耍吧,少年!

$dirname = dirname(__FILE__);
ini_set('filebase.root', $dirname.'/filebase');
ini_set('filebase.debug', '1');

$article = filebase_get('article', '12345');
if ($article)
{
    echo $article;
}
else
{
    echo "Error\n";
}

$article = "This is a very long text";
$length = filebase_put('article', '54321', $article);
echo "write len {$length}\n";

$res = filebase_del('article', '54321');
if ($res)
{
    echo "Success\n";
}
else
{
    echo "Failure\n";
}

github地址:https://github.com/gerpayt/php-filebase

———-效率测试———

可想而知,这个插件的性能应该高于file_get_contents(),事实证明,比file_get_contents快10%左右。
其实不是为了提高这10%的性能而编写这个扩展,而是为了方便管理,备份,迁移,符合大数据的思想。

One thought on “自己动手写php扩展

Leave a reply