我慢強い人のためのpeclでsession_handlerを作る。

session_handler作ってますか?
セッションをファイル以外に格納するとき使いますね。
例えば、セッションをデータベースとか、KVS等のセッションを格納するときとか。


ほとんどの場合は php で session_handlerをシコシコ作っていると思います。
が、phpでセッションハンドラーを作ると、パフォーマンス的にあれだったり、毎回作るのがめんどかったり、もっと高度なことをしたくなったりするヂャないですか。 Yes/Y
それだったら、 peclPHP拡張として、C言語で書くしかないですよね。


pecl なセッションハンドラに対応している pecl 拡張の場合、、、 session.save_handler を定義してあげることで、簡単に導入できます。

//tokyo tyrant にセッションストアをしたい時、、、
ini_set('session.save_handler', 'tokyo_tyrant');
ini_set('session.save_path', 'tcp://127.0.0.1:1978');

このサンプルを作るのには、 pecl tokyotyrant と ext/session/mod_files.c を参考にしています。

動作するサンプルのダウンロード

プログラム
http://rtilabs.net/files/2011_02_13/mytest_session.tar.gz
webの画面
http://rtilabs.net/files/2011_02_13/webtest.tphp

0.困ったときに泣きつく先

・セッション周りの定義がされています。
php/ext/session/php_session.h

・ディフォルトのセッションハンドラー files の実装です。
php/ext/session/mod_files.c

・動いている実装を参考にwww
pecl tokyotyrantのソースとかw

・最終兵器ロガーによるprintfデバッグww
printfデバッグのお供に
http://d.hatena.ne.jp/rti7743/20100604/1275665317


1.環境を作る。

ここでは、前回の「魔法少女好きだったらわかるphp拡張の作り方。」で作成したモジュールをさらに拡張させて、セッションハンドラを作成します。
とりあえず、 mytest.so が出来るところまで勧めてください。

2. セッションハンドラー用のプログラムを書く

今回は、/tmp/ の下にファイルを作るという php のディフォルトのセッションストアをリスペクトしたものをガチで実装したいと思います。


mytest_session.c と mytest_session.h の2つのファイルを作成します。
ファイルの文字コードUTF-8 で作ってください。


まず mytest_session.h を作るよ。

#ifndef MYTEST_SESSION_H
#define MYTEST_SESSION_H

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_mytest.h"
#include <fcntl.h>

/* セッション管理用のデータ構造 */
typedef struct  {
	char * filename;
	char * save_path;
} php_mytest_session;

#endif //MYTEST_SESSION_H

次に、mytest_session.c を作るよ。
こいつはでかいから危険が危ないよ。

vi mytest_session.c
--------------------------------------------------------------------
#include "mytest_session.h"

/*
	セッションキーでイタヅラをされないようにチェックする.
	php 本体と同じ規則でチェックする.
	/a-zA-Z0-9,\-/ が OK
	長さもバッファを突破しないように.
*/
static int checkSessionKey(const char * key)
{
	const char * p;
	for(p = key ; *p ; p ++)
	{
		if (! ( (*p >= 'a' && *p <= 'z')
			 || (*p >= 'A' && *p <= 'Z')
			 || (*p >= '0' && *p <= '9')
			 || *p == ','
			 || *p == '-'
			))
		{
			/* 無効な文字列 */
			return 0;
		}
	}

	if ( (int)(p - key) > MAXPATHLEN - 20 || key[0] == '\0') 
	{   /*
			-20は /tmp/mytest... などの分の目安w 
			うわっ、私のセッション長すぎ!
		*/
		return 0;
	}
	return 1;
}

/**
セッション用のユニークなランダムな文字列 sid を返す.
目的
	1.セッション用のsid (ランダムなユニークな文字列)を emalloc して返す.

注意
	一見単純そうだが、session_regenerate_idの 罠がある。
	ここが呼ばれ時、セッションが有効な場合と無効な場合がある。
	また一見有効だが、実は無効など、、複雑な場合がある。
	つまり、
	動作1.まったくサラ地の状態でセッションを作る場合 (大部分の場合)
	動作2.セッションは有効で利用できるが、新たに作り直す場合
	動作3.セッションは有効とあるが、実は無効なデータ NULL が入っている場合 session_regenerate_id(TRUE)

	サーバに対する connection pool等をしている場合は要注意である。
	詳しくは、 pecl tokyotyrant の session.c とかを見ればいいよ.
定義
	char *ps_create_sid_##x(void **mod_data, int *newlen TSRMLS_DC)
*/
PS_CREATE_SID_FUNC(mytest)
{
	/*
	php本体のphp_session_create_idを呼び出してユニークな SID をつくってもらう。
	自分で作ってもいいんだろうけど、、、
	*/
	char *sid = php_session_create_id(PS(mod_data), NULL TSRMLS_CC);
	if (!sid)
	{
		php_error_docref(NULL TSRMLS_CC, E_ERROR, "PS_CREATE_SID_FUNC(mytest) SIDがつくれないよ!!");
		return NULL;
	}
	return sid;
}


/**
セッション管理が開始されるとき呼ばれる。
目的
	1.セッション用のメモリの確保
		session = emalloc( sizeof( php_mytest_session ) );
		PS_SET_MOD_DATA(session)
			以後 PS_GET_MOD_DATA() で取り出せる.
	2.格納する場所の初期化を行う。 PS(save_path)

注意
	PS_OPEN_FUNCと名前があるが、
	ここでは、sid がまだわからないので、セッションを open できない。
	なお、connection pool等をここで開始する実装もある。
定義
	int ps_open_##x(void **mod_data, const char *save_path, const char *session_name TSRMLS_DC)
*/
PS_OPEN_FUNC(mytest)
{
	/* セッション用のメモリを確保 */
	php_mytest_session * session = (php_mytest_session*)emalloc( sizeof( php_mytest_session ) );
	if (!session)
	{
		PS_SET_MOD_DATA(NULL);
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_OPEN_FUNC(mytest) セッションが取れないよ");
		return FAILURE;
	}
	/* セッションをセーブするパス */
	session->save_path = estrndup( save_path, strlen( save_path ));

	/* サーバタイプの場合ここで接続することもできる. */

	/* セッションデータの記録 */
	PS_SET_MOD_DATA(session);
	return SUCCESS;
}


/**
セッション管理を開放する時に呼ばれる.
目的
	1.セッション用のメモリの開放
		PS_OPEN_FUNC で確保した領域の開放を行う。

注意
		あとででくる PS_DESTORY_FUNC と紛らわしいが、
		Close の目的は、 Openの逆である。
定義
	int ps_close_##x(void **mod_data TSRMLS_DC)
*/
PS_CLOSE_FUNC(mytest)
{
	/* セッション管理データの読み出し. */
	php_mytest_session * session = PS_GET_MOD_DATA();
	if (!session)
	{
		/* closeなので甘めに. */
		return SUCCESS;
	}

	/* PS_OPEN_FUNCで確保したデータを解放. */
	efree(session->save_path);
	efree(session);

	/* データには、何もありませんよ。 */
	session = NULL;
	PS_SET_MOD_DATA(NULL);

	return SUCCESS;
}


/**
セッションに対する読み込み
目的
	1.セッションデータ key が指すデータを、PS_OPEN_FUNC で準備した
		 PS_GET_MOD_DATA() を使って読み込む
	2.読み込んだデータ *val に書きこむ。
	3.読み込んだデータ長さは *vallen に書く.
定義
	int ps_read_##x(void **mod_data, const char *key, char **val, int *vallen TSRMLS_DC)
*/
PS_READ_FUNC(mytest)
{
	/* セッション管理データの読み出し. */
	php_mytest_session * session = PS_GET_MOD_DATA();
	if (!session)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_READ_FUNC(mytest) セッションが取れないよ");
		return FAILURE;
	}

	/* 引数 key でイタヅラされないように. */
	if ( ! checkSessionKey(key) )
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_READ_FUNC(mytest) key %s は無効な文字列です" , key);
		return FAILURE;
	}

	/* 引数 key が指し示すデータを開く. */
	char filename[MAXPATHLEN];
	snprintf(filename,MAXPATHLEN,"%s/mytest.session.%s.txt",session->save_path , key );
	filename[MAXPATHLEN - 1] = '\0';

	FILE * fp = fopen(filename,"ab+");
	if (!fp)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_READ_FUNC(mytest) ファイル %s が開けないよ! %s %d" , filename,strerror(errno), errno);
		return FAILURE;
	}
	flock(fp, LOCK_EX);

	//先頭に戻す.
	rewind(fp);

	struct stat sbuf;
	if (fstat(fileno(fp), &sbuf))
	{
		fclose(fp);
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_READ_FUNC(mytest) ファイル %s の fstat が取れないよ" ,filename,strerror(errno), errno);
		return FAILURE;
	}

	/* データの長さが 0 の場合は特殊な処理をする. */
	if (sbuf.st_size == 0)
	{
		flock(fp, LOCK_UN);
		fclose(fp);
		*val = STR_EMPTY_ALLOC();
		*vallen = 0;
		return SUCCESS;
	}
	
	/* データ長分の領域を確保.*/
	char* readbuffer = (char*)emalloc(sbuf.st_size);
	int readsize = fread(readbuffer,1,sbuf.st_size,fp);

	/* もうデータは不要なのでファイルを閉じる. */
	flock(fp, LOCK_UN);
	fclose(fp);
	
	/* 読めたかな? */
	if (readsize != sbuf.st_size)
	{
		/* 読めんかった。。 */
		efree(readbuffer);
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_READ_FUNC(mytest) ファイル %s からデータが読み取れないよ" , filename);
		return FAILURE;
	}

	/* 読み込んだデータを返す. (readbufferを開放するのは、phpさんの責任!!) */
	*val = readbuffer;
	*vallen = readsize;

	/* やったー!! */
	return SUCCESS;
}


/**
セッションに対する書き込み
目的
	1.セッションデータ key のデータ val を、PS_OPEN_FUNC で準備した
		 PS_GET_MOD_DATA() を使って書きこむ
	2.データ val のデータの長さ vallen になる.
定義
	int ps_write_##x(void **mod_data, const char *key, const char *val, const int vallen TSRMLS_DC)
*/
PS_WRITE_FUNC(mytest)
{
	/* セッション管理データの読み出し. */
	php_mytest_session * session = PS_GET_MOD_DATA();
	if (!session)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_WRITE_FUNC(mytest) セッションが取れないよ");
		return FAILURE;
	}

	/* 引数 key でイタヅラされないように. */
	if ( ! checkSessionKey(key) )
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_WRITE_FUNC(mytest) key %s は無効な文字列です" , key);
		return FAILURE;
	}

	/* 引数 key が指し示すデータを開く. */
	char filename[MAXPATHLEN];
	snprintf(filename,MAXPATHLEN,"%s/mytest.session.%s.txt",session->save_path , key );
	filename[MAXPATHLEN - 1] = '\0';

	FILE * fp = fopen(filename,"wb+");
	if (!fp)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_WRITE_FUNC(mytest) ファイル %s が開けないよ! %s %d" , filename,strerror(errno), errno);
		return FAILURE;
	}
	flock(fp, LOCK_EX);

	/* データ長分の領域を確保. */
	int writesize = fwrite(val,1,vallen,fp);

	/* もうデータは不要なのでファイルを閉じる. */
	flock(fp, LOCK_UN);
	fclose(fp);
	
	/* かけたかな? */
	if (writesize != vallen)
	{
		/* かけんかった、、、 */
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_WRITE_FUNC(mytest) ファイル %s にかけんかったよ!" , filename);
		return FAILURE;
	}

	return SUCCESS;
}

/**
セッションデータを削除する
目的
	1.セッションデータ key のデータを削除する
注意
	PS_CLOSE_FUNCとは違い、PS_SET_MOD_DATA(NULL)する「わけではない」。
	あくまでも、 key のデータを消去するだけである。
定義
	int ps_delete_##x(void **mod_data, const char *key TSRMLS_DC)
*/
PS_DESTROY_FUNC(mytest)
{
	/* セッション管理データの読み出し. */
	php_mytest_session * session = PS_GET_MOD_DATA();
	if (!session)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_DESTROY_FUNC(mytest) セッションが取れないよ");
		return FAILURE;
	}

	/* 引数 key でイタヅラされないように. */
	if ( ! checkSessionKey(key) )
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_DESTROY_FUNC(mytest) key %s は無効な文字列です" , key);
		return FAILURE;
	}

	/* 引数 key が指し示すデータを開く. */
	char filename[MAXPATHLEN];
	snprintf(filename,MAXPATHLEN,"%s/mytest.session.%s.txt",session->save_path , key );
	filename[MAXPATHLEN - 1] = '\0';

	/* データ key のデータが入っているファイルを削除する. */
	unlink(filename);

	return SUCCESS;
}


/*
ガページコレクションを行う。
目的
	1.古いデータファイルを削除する
	2.古いデータの定義は、引数 maxlifetime (秒)を過ぎたデータである。
注意
	PHPのセッションは、特定のユーザセッションを利用して、セッションの GC を起動する。
	重いGCを動かすとユーザ体験の阻害になる可能性がある。
	GC が発動する確率は、 PS(gc_probability) / PS(gc_divisor) である。
定義
	int ps_gc_##x(void **mod_data, int maxlifetime, int *nrdels TSRMLS_DC)
*/
PS_GC_FUNC(mytest)
{
	/* セッション管理データの読み出し. 
	save_path を取り出すためだけに利用する... */
	php_mytest_session * session = PS_GET_MOD_DATA();
	if (!session)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_GC_FUNC(mytest) セッションが取れないよ");
		return FAILURE;
	}
	
	/* GCするためにディレクトリを開く。
	ここにある古いファイルを削除していく. */
	DIR *dir = opendir(session->save_path);
	if (!dir)
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "PS_GC_FUNC(mytest) ディレクトリ %s が開けないよ! %s %d" , session->save_path,strerror(errno), errno);
		return FAILURE;
	}

	/* 現在時刻。
	この時刻をベースに、 引数 maxlifetime (秒) 過ぎているデータを削除していく. */
	time_t now = time(NULL);
	char filename[MAXPATHLEN];

	struct dirent *dire;
	while ((dire = readdir(dir)) != NULL )
	{
		/* セッションデータ以外は無視する. */
		if ( strcmp(dire->d_name , "mytest.session.") != 0)
		{
			continue;
		}

		/* ファイル名の生成 */
		snprintf(filename,MAXPATHLEN,"%s/mytest.session.%s.txt",session->save_path , dire->d_name );
		filename[MAXPATHLEN - 1] = '\0';

		struct stat sbuf;
		if (stat(filename , &sbuf))
		{
			/* 何故か日付とかが読めない... */
			continue;
		}
		
		/* 古いデータ? */
		if ((now - sbuf.st_mtime) <= maxlifetime)
		{
			/* 古くない. */
			continue;
		}

		/* データを消去する. */

		unlink(filename);
	}

	return SUCCESS;
}



/*
  session_handler を実現するためのおまじない. 
  これは、マクロ展開されて、 PS_CREATE_SID_FUNC などで定義した関数へのルックアップテーブルになるよ。
定義
	typedef struct ps_module_struct {
		const char *s_name;
		int (*s_open)(PS_OPEN_ARGS);
		int (*s_close)(PS_CLOSE_ARGS);
		int (*s_read)(PS_READ_ARGS);
		int (*s_write)(PS_WRITE_ARGS);
		int (*s_destroy)(PS_DESTROY_ARGS);
		int (*s_gc)(PS_GC_ARGS);
		char *(*s_create_sid)(PS_CREATE_SID_ARGS);
	} ps_module;
*/
ps_module ps_mod_mytest = {
  PS_MOD(mytest)
};

-------------------------------------------------------------------------


長いね。
とりあえず動かすことを前提にすすめるので解説はあとでやろう。

3 php_mytest.h の変更

php_mytest.h のこと覚えてる?
mytest.so のコアになったヘッダーファイルだよ。


最後の方の1箇所変更するよ。

vi php_mytest.h
-------------------------------------------------------------------------
#ifndef PHP_MYTEST_H
#define PHP_MYTEST_H

extern zend_module_entry mytest_module_entry;
#define phpext_mytest_ptr &mytest_module_entry

#ifdef PHP_WIN32
#	define PHP_MYTEST_API __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#	define PHP_MYTEST_API __attribute__ ((visibility("default")))
#else
#	define PHP_MYTEST_API
#endif

#ifdef ZTS
#include "TSRM.h"
#endif

PHP_MINIT_FUNCTION(mytest);
PHP_MSHUTDOWN_FUNCTION(mytest);
PHP_RINIT_FUNCTION(mytest);
PHP_RSHUTDOWN_FUNCTION(mytest);
PHP_MINFO_FUNCTION(mytest);

PHP_FUNCTION(confirm_mytest_compiled);	/* For testing, remove later. */
PHP_FUNCTION(mytest_mahou_syoujyo);     /* 追加追加追加追加追加追加追加 */


/* 
  	Declare any global variables you may need between the BEGIN
	and END macros here:     

ZEND_BEGIN_MODULE_GLOBALS(mytest)
	long  global_value;
	char *global_string;
ZEND_END_MODULE_GLOBALS(mytest)
*/

/* In every utility function you add that needs to use variables 
   in php_mytest_globals, call TSRMLS_FETCH(); after declaring other 
   variables used by that function, or better yet, pass in TSRMLS_CC
   after the last function argument and declare your utility function
   with TSRMLS_DC after the last declared argument.  Always refer to
   the globals in your function as MYTEST_G(variable).  You are 
   encouraged to rename these macros something shorter, see
   examples in any other php module directory.
*/

#ifdef ZTS
#define MYTEST_G(v) TSRMG(mytest_globals_id, zend_mytest_globals *, v)
#else
#define MYTEST_G(v) (mytest_globals.v)
#endif

/* 
   追加追加追加追加追加追加追加追加追加追加

   セッション関係のロード
*/
#include "ext/session/php_session.h"
extern ps_module ps_mod_mytest;
/*
   ここまで変更
*/

#endif	/* PHP_MYTEST_H */
-----------------------------------------------------------------

4 mytest.c の変更

session handler の変更もするよって通知してあげる所を追加するよ。
これも最後の方に1箇所変更があるよ。

vi mytest.c
-----------------------------------------------------------------
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
#include "php_mytest.h"

/* If you declare any globals in php_mytest.h uncomment this:
ZEND_DECLARE_MODULE_GLOBALS(mytest)
*/

/* True global resources - no need for thread safety here */
static int le_mytest;

/* {{{ mytest_functions[]
 *
 * Every user visible function must have an entry in mytest_functions[].
 */
const zend_function_entry mytest_functions[] = {
	PHP_FE(confirm_mytest_compiled,	NULL)		/* For testing, remove later. */
        PHP_FE(mytest_mahou_syoujyo,    NULL)           /* For moemoe chun. ←追加追加追加追加 */  
	{NULL, NULL, NULL}	/* Must be the last line in mytest_functions[] */
};
/* }}} */

/* {{{ mytest_module_entry
 */
zend_module_entry mytest_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
	STANDARD_MODULE_HEADER,
#endif
	"mytest",
	mytest_functions,
	PHP_MINIT(mytest),
	PHP_MSHUTDOWN(mytest),
	PHP_RINIT(mytest),		/* Replace with NULL if there's nothing to do at request start */
	PHP_RSHUTDOWN(mytest),	/* Replace with NULL if there's nothing to do at request end */
	PHP_MINFO(mytest),
#if ZEND_MODULE_API_NO >= 20010901
	"0.1", /* Replace with version number for your extension */
#endif
	STANDARD_MODULE_PROPERTIES
};
/* }}} */

#ifdef COMPILE_DL_MYTEST
ZEND_GET_MODULE(mytest)
#endif

/* {{{ PHP_INI
 */
/* Remove comments and fill if you need to have entries in php.ini
PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("mytest.global_value",      "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_mytest_globals, mytest_globals)
    STD_PHP_INI_ENTRY("mytest.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_mytest_globals, mytest_globals)
PHP_INI_END()
*/
/* }}} */

/* {{{ php_mytest_init_globals
 */
/* Uncomment this function if you have INI entries
static void php_mytest_init_globals(zend_mytest_globals *mytest_globals)
{
	mytest_globals->global_value = 0;
	mytest_globals->global_string = NULL;
}
*/
/* }}} */

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(mytest)
{
	/* If you have INI entries, uncomment these lines 
	REGISTER_INI_ENTRIES();
	*/

	/* 
	追加追加追加追加追加追加追加追加追加追加追加追加追加追加追加
	セッション用モジュールをロードするですよ. 
	*/
	php_session_register_module(&ps_mod_mytest);

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(mytest)
{
	/* uncomment this line if you have INI entries
	UNREGISTER_INI_ENTRIES();
	*/
	return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request start */
/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(mytest)
{
	return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request end */
/* {{{ PHP_RSHUTDOWN_FUNCTION
 */
PHP_RSHUTDOWN_FUNCTION(mytest)
{
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(mytest)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "mytest support", "enabled");
	php_info_print_table_end();

	/* Remove comments if you have entries in php.ini
	DISPLAY_INI_ENTRIES();
	*/
}
/* }}} */


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

/* Every user-visible function in PHP should document itself in the source */
/* {{{ proto string confirm_mytest_compiled(string arg)
   Return a string to confirm that the module is compiled in */
PHP_FUNCTION(confirm_mytest_compiled)
{
	char *arg = NULL;
	int arg_len, len;
	char *strg;

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
		return;
	}

	len = spprintf(&strg, 0, "さくらちゃんと結婚したい! Congratulations! You have successfully modified ext/%.78s/config.m4. Module %.78s is now compiled into PHP.", "mytest", arg);
	RETURN_STRINGL(strg, len, 0);
}
/* }}} */

/* 以下全部追加! */
/* {{{ proto string mytest_mahou_syoujyo(string arg)
   */
PHP_FUNCTION(mytest_mahou_syoujyo)
{
        char *arg = NULL;
        int arg_len, len;
        char *strg;

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &arg, &arg_len) == FAILURE) {
                return;
        }
        if ( strcmp(arg,"さくら") == 0)
        {
                len = spprintf(&strg, 0,"はにゃーん");
        }
        else
        {
                len = spprintf(&strg, 0,"さくら %s ぢゃないもんっ",arg);
        }

        RETURN_STRINGL(strg, len, 0);
}
/* }}} */

/* The previous line is meant for vim and emacs, so it can correctly fold and 
   unfold functions in source code. See the corresponding marks just before 
   function definition, where the functions purpose is also documented. Please 
   follow this convention for the convenience of others editing your code.
*/
-----------------------------------------------------------------

5. ラスト! config.m4 の修正

末尾の一行を修正します。
そうしないと、セッションを管理するながったらしい mytest_session.c がコンパイル対象になりません。

vi config.m4
-------------------------------------------------------------------------
  PHP_NEW_EXTENSION(mytest, mytest.c, $ext_shared)

  ↓↓↓↓↓↓↓↓↓変更↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

  PHP_NEW_EXTENSION(mytest, mytest.c mytest_session.c, $ext_shared)
-------------------------------------------------------------------------

6. できたー

動かしてみましょう!!

make clean
phpize
./configure
make

ここまででエラーないですか?
エラーがないなら、 インストールしてみましょう。
session周りなので、ブラウザ越しにしかテストできないです。

7.インストール

make install

これで、 /usr/lib/php/modules/mytest.so にコピーされます。

8. 拡張を phpが読み込むようにする。

拡張を php がロードできるように、 extension に書いてあげます。

vi /etc/php.d/mytest.ini
----------------------------------------
extension=mytest.so
----------------------------------------

9. httpd リスタート!!

/etc/init.d/httpd restart

10.実行する php を設置する.

以下のような php を webroot の下に設置します。
内容は、セッションを利用して、リロードするたびに数字を coutnupさせるものです。

---------------------------------------------------------
<?php
//mytest を利用する
ini_set('session.save_handler', 'mytest');
ini_set('session.save_path', '/tmp');

session_start();

if ( isset ( $_GET['renew'] ) )
{
	$old_session_id = session_id();
	$result_session_regenerate_id = session_regenerate_id(TRUE);
	$new_session_id = session_id();

	echo "REGENERATE!!<br>\r\n";
	echo "old_session_id:{$old_session_id}<br>\r\n";
	echo "new_session_id:{$new_session_id}<br>\r\n";
	echo "session_regenerate_id return :{$result_session_regenerate_id}<br>\r\n";
	echo "<br>\r\n";
}
else
{
	$new_session_id = session_id();
}

$a = (int)@$_SESSION['a'];
@$_SESSION['a'] = $a + 1;

echo "Count : {$a}<br>\r\n";
echo "Session : {$new_session_id}<br>\r\n";
?>
<br>
<br>
<br>
<a href="?">countup : reload or click</a><br>
<a href="?renew">session_regenerate_id</a><br>
<br>
---------------------------------------------------------

11.ブラウザからさっきの php を閲覧する!!


エラーが表示させれずに、数字がカウントアップされれば成功です!!!!!!
お疲れさまでした。


エラーが表示されてしまったというあなた、、いったい何をやらかしたんですかー。
どこかまずいところがあったと思います。
もしかしたら、バグかもしれません。掲示板で教えていただけると幸いです。

12.セッションの確認

うまく動作すると、 /tmp/の下に以下のようなセッションが作られます。
phpのディフォルトのセッションは sess_* という名前なので、 今回作った mytest セッションハンドラーが正しく動作していることがわかります。

ll /tmp/
--------------------------------------------------
mytest.session.5b6173nhv0t9c3vg5g46sh1ve6.txt
--------------------------------------------------

やったね☆
バレンタインチョコ欲しい!