]> granicus.if.org Git - php/commitdiff
Implement object-oriented session handlers (https://wiki.php.net/rfc/session-oo)
authorArpad Ray <arpad@php.net>
Tue, 13 Sep 2011 22:28:15 +0000 (22:28 +0000)
committerArpad Ray <arpad@php.net>
Tue, 13 Sep 2011 22:28:15 +0000 (22:28 +0000)
24 files changed:
NEWS
ext/session/config.m4
ext/session/config.w32
ext/session/mod_user.c
ext/session/mod_user_class.c [new file with mode: 0644]
ext/session/package.xml
ext/session/php_session.h
ext/session/session.c
ext/session/tests/session_set_save_handler_class_001.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_002.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_003.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_004.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_005.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_006.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_007.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_008.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_009.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_010.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_011.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_012.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_013.phpt [new file with mode: 0644]
ext/session/tests/session_set_save_handler_class_014.phpt [new file with mode: 0644]
ext/standard/basic_functions.c
ext/standard/basic_functions.h

diff --git a/NEWS b/NEWS
index 92dcfe705d1a8ef96eebaf7d4e43a5052bec305b..41f394da1c2bc1407a81bf37097c6d2ab2cd670b 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -54,6 +54,7 @@ PHP                                                                        NEWS
 
 - Improved Session extension:
   . Expose session status via new function, session_status (FR #52982) (Arpad)
+  . Added support for object-oriented session handlers. (Arpad)
 
 - Improved SPL extension:
   . Immediately reject wrong usages of directories under Spl(Temp)FileObject
index d65e872a3a48f503d323d908b05525c67d512eee..1c3ba783680583ba58d8ab93bb661f94dc0df651 100644 (file)
@@ -11,7 +11,7 @@ PHP_ARG_WITH(mm,for mm support,
 if test "$PHP_SESSION" != "no"; then
   PHP_PWRITE_TEST
   PHP_PREAD_TEST
-  PHP_NEW_EXTENSION(session, session.c mod_files.c mod_mm.c mod_user.c, $ext_shared)
+  PHP_NEW_EXTENSION(session, mod_user_class.c session.c mod_files.c mod_mm.c mod_user.c, $ext_shared)
   PHP_ADD_EXTENSION_DEP(session, hash, true)
   PHP_ADD_EXTENSION_DEP(session, spl)
   PHP_SUBST(SESSION_SHARED_LIBADD)
index 531fed1fb66189d749bfa19bbdab22417a097b25..c8b217aad9f0ed325e63524b18ae7a0ae1eb2e71 100644 (file)
@@ -4,7 +4,7 @@
 ARG_ENABLE("session", "session support", "yes");
 
 if (PHP_SESSION == "yes") {
-       EXTENSION("session", "session.c mod_files.c mod_mm.c mod_user.c", false /* never shared */);
+       EXTENSION("session", "mod_user_class.c session.c mod_files.c mod_mm.c mod_user.c", false /* never shared */);
        AC_DEFINE("HAVE_PHP_SESSION", 1, "Session support");
        PHP_INSTALL_HEADERS("ext/session/", "mod_mm.h php_session.h mod_files.h mod_user.h");
 }
index 61a3586bd0beacba18aba804fda98f335f4c0dbf..f643b9eee6f78ab82570c187c26fff5d858b92cb 100644 (file)
@@ -62,15 +62,10 @@ static zval *ps_call_handler(zval *func, int argc, zval **argv TSRMLS_DC)
        return retval;
 }
 
-#define STDVARS1                                                       \
+#define STDVARS                                                                \
        zval *retval;                                                   \
        int ret = FAILURE
 
-#define STDVARS                                                                \
-       STDVARS1;                                                               \
-       char *mdata = PS_GET_MOD_DATA();                \
-       if (!mdata) { return FAILURE; }
-
 #define PSF(a) PS(mod_user_names).name.ps_##a
 
 #define FINISH                                                         \
@@ -84,32 +79,28 @@ static zval *ps_call_handler(zval *func, int argc, zval **argv TSRMLS_DC)
 PS_OPEN_FUNC(user)
 {
        zval *args[2];
-       static char dummy = 0;
-       STDVARS1;
+       STDVARS;
 
        SESS_ZVAL_STRING((char*)save_path, args[0]);
        SESS_ZVAL_STRING((char*)session_name, args[1]);
 
        retval = ps_call_handler(PSF(open), 2, args TSRMLS_CC);
-       if (retval) {
-               /* This is necessary to fool the session module. Yes, it's safe to
-                * use a static. Neither mod_user nor the session module itself will
-                * ever touch this pointer. It could be set to 0xDEADBEEF for all the
-                * difference it makes, but for the sake of paranoia it's set to some
-                * valid value. */
-               PS_SET_MOD_DATA(&dummy);
-       }
+       PS(mod_user_implemented) = 1;
 
        FINISH;
 }
 
 PS_CLOSE_FUNC(user)
 {
-       STDVARS1;
+       STDVARS;
 
-       retval = ps_call_handler(PSF(close), 0, NULL TSRMLS_CC);
+       if (!PS(mod_user_implemented)) {
+               /* already closed */
+               return SUCCESS;
+       }
 
-       PS_SET_MOD_DATA(NULL);
+       retval = ps_call_handler(PSF(close), 0, NULL TSRMLS_CC);
+       PS(mod_user_implemented) = 0;
 
        FINISH;
 }
diff --git a/ext/session/mod_user_class.c b/ext/session/mod_user_class.c
new file mode 100644 (file)
index 0000000..efc39c3
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+   +----------------------------------------------------------------------+
+   | PHP Version 5                                                        |
+   +----------------------------------------------------------------------+
+   | Copyright (c) 1997-2011 The PHP Group                                |
+   +----------------------------------------------------------------------+
+   | This source file is subject to version 3.01 of the PHP license,      |
+   | that is bundled with this package in the file LICENSE, and is        |
+   | available through the world-wide-web at the following url:           |
+   | http://www.php.net/license/3_01.txt                                  |
+   | If you did not receive a copy of the PHP license and are unable to   |
+   | obtain it through the world-wide-web, please send a note to          |
+   | license@php.net so we can mail you a copy immediately.               |
+   +----------------------------------------------------------------------+
+   | Author: Arpad Ray <arpad@php.net>                                    |
+   +----------------------------------------------------------------------+
+ */
+
+/* $Id$ */
+
+#include "php.h"
+#include "php_session.h"
+
+#define PS_SANITY_CHECK                                                \
+       if (PS(default_mod) == NULL) {                          \
+               php_error_docref(NULL TSRMLS_CC, E_CORE_ERROR, "Called default SessionHandler but session.save_handler is user"); \
+               RETURN_FALSE;                                           \
+       }                                                       
+
+#define PS_SANITY_CHECK_IS_OPEN                                \
+       PS_SANITY_CHECK; \
+       if (!PS(mod_user_is_open)) {                    \
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Parent session handler is not open");      \
+               RETURN_FALSE;                                           \
+       }                                                       
+
+/* {{{ proto bool SessionHandler::open(string save_path, string session_name)
+   Wraps the old open handler */
+PHP_METHOD(SessionHandler, open)
+{
+       char *save_path = NULL, *session_name = NULL;
+       int save_path_len, session_name_len;
+
+       PS_SANITY_CHECK;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &save_path, &save_path_len, &session_name, &session_name_len) == FAILURE) {
+               return;
+       }
+
+       PS(mod_user_is_open) = 1;
+       RETVAL_LONG(PS(default_mod)->s_open(&PS(mod_data), save_path, session_name TSRMLS_CC));
+}
+/* }}} */
+
+/* {{{ proto bool SessionHandler::close()
+   Wraps the old close handler */
+PHP_METHOD(SessionHandler, close)
+{
+       PS_SANITY_CHECK_IS_OPEN;
+
+       // don't return on failure, since not closing the default handler
+       // could result in memory leaks or other nasties
+       zend_parse_parameters_none();
+       
+       PS(mod_user_is_open) = 0;
+       RETVAL_LONG(PS(default_mod)->s_close(&PS(mod_data) TSRMLS_CC));
+}
+/* }}} */
+
+/* {{{ proto bool SessionHandler::read(string id)
+   Wraps the old read handler */
+PHP_METHOD(SessionHandler, read)
+{
+       char *key, *val;
+       int key_len, val_len;
+
+       PS_SANITY_CHECK_IS_OPEN;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) == FAILURE) {
+               return;
+       }
+
+       if (PS(default_mod)->s_read(&PS(mod_data), key, &val, &val_len TSRMLS_CC) == FAILURE) {
+               RETVAL_FALSE;
+               return;
+       }
+
+       RETVAL_STRINGL(val, val_len, 1);
+       efree(val);
+       return;
+}
+/* }}} */
+
+/* {{{ proto bool SessionHandler::write(string id, string data)
+   Wraps the old write handler */
+PHP_METHOD(SessionHandler, write)
+{
+       char *key, *val;
+       int key_len, val_len;
+
+       PS_SANITY_CHECK_IS_OPEN;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &key, &key_len, &val, &val_len) == FAILURE) {
+               return;
+       }
+
+       RETVAL_LONG(PS(default_mod)->s_write(&PS(mod_data), key, val, val_len TSRMLS_CC));
+}
+/* }}} */
+
+/* {{{ proto bool SessionHandler::destroy(string id)
+   Wraps the old destroy handler */
+PHP_METHOD(SessionHandler, destroy)
+{
+       char *key;
+       int key_len;
+
+       PS_SANITY_CHECK_IS_OPEN;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_len) == FAILURE) {
+               return;
+       }
+       
+       PS(mod_user_is_open) = 0;
+       RETVAL_LONG(PS(default_mod)->s_destroy(&PS(mod_data), key TSRMLS_CC));
+}
+/* }}} */
+
+/* {{{ proto bool SessionHandler::gc(int maxlifetime)
+   Wraps the old gc handler */
+PHP_METHOD(SessionHandler, gc)
+{
+       long maxlifetime;
+       int nrdels;
+
+       PS_SANITY_CHECK;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &maxlifetime) == FAILURE) {
+               return;
+       }
+       
+       RETVAL_LONG(PS(default_mod)->s_gc(&PS(mod_data), maxlifetime, &nrdels TSRMLS_CC));
+}
+/* }}} */
index fdf85fc4470193a6fd986f0b367644756acf0f1b..03fcbf66daf8b4e467a5b9b81c92004c5ad9c95e 100644 (file)
@@ -40,6 +40,7 @@ package.xml added to support installation using pear installer
    <file role="src" name="mod_mm.c"/>
    <file role="src" name="mod_mm.h"/>
    <file role="src" name="mod_user.c"/>
+   <file role="src" name="mod_user_class.c"/>
    <file role="src" name="mod_user.h"/>
    <file role="src" name="php_session.h"/>
    <file role="src" name="session.c"/>
index 767ed48c888718ca4b1a6d69fe57b4120db877a7..b11aad207449c57a9bbbcd2b209c3ea70e4bf3f2 100644 (file)
@@ -129,6 +129,7 @@ typedef struct _php_ps_globals {
        zend_bool  cookie_secure;
        zend_bool  cookie_httponly;
        ps_module *mod;
+       ps_module *default_mod;
        void *mod_data;
        php_session_status session_status;
        long gc_probability;
@@ -147,6 +148,8 @@ typedef struct _php_ps_globals {
                        zval *ps_gc;
                } name;
        } mod_user_names;
+       int mod_user_implemented;
+       int mod_user_is_open;
        const struct ps_serializer_struct *serializer;
        zval *http_session_vars;
        zend_bool auto_start;
@@ -268,4 +271,14 @@ PHPAPI ZEND_EXTERN_MODULE_GLOBALS(ps)
 void php_session_auto_start(void *data);
 void php_session_shutdown(void *data);
 
+#define PS_CLASS_NAME "SessionHandler"
+extern zend_class_entry *php_session_class_entry;
+
+extern PHP_METHOD(SessionHandler, open);
+extern PHP_METHOD(SessionHandler, close);
+extern PHP_METHOD(SessionHandler, read);
+extern PHP_METHOD(SessionHandler, write);
+extern PHP_METHOD(SessionHandler, destroy);
+extern PHP_METHOD(SessionHandler, gc);
+
 #endif
index 4e2578a44750aadf58b06e9b09519b207536d857..bd4efd0cdb25ea9c68827a9a65b63cc7a58169d7 100644 (file)
@@ -50,6 +50,7 @@
 #include "ext/standard/info.h"
 #include "ext/standard/php_smart_str.h"
 #include "ext/standard/url.h"
+#include "ext/standard/basic_functions.h"
 
 #include "mod_files.h"
 #include "mod_user.h"
@@ -63,6 +64,9 @@ PHPAPI ZEND_DECLARE_MODULE_GLOBALS(ps);
 static int php_session_rfc1867_callback(unsigned int event, void *event_data, void **extra TSRMLS_DC);
 static int (*php_session_rfc1867_orig_callback)(unsigned int event, void *event_data, void **extra TSRMLS_DC);
 
+/* SessionHandler class */
+zend_class_entry *php_session_class_entry;
+
 /* ***********
    * Helpers *
    *********** */
@@ -82,6 +86,7 @@ static inline void php_rinit_session_globals(TSRMLS_D) /* {{{ */
        PS(id) = NULL;
        PS(session_status) = php_session_none;
        PS(mod_data) = NULL;
+       PS(mod_user_is_open) = 0;
        /* Do NOT init PS(mod_user_names) here! */
        PS(http_session_vars) = NULL;
 }
@@ -95,7 +100,7 @@ static inline void php_rshutdown_session_globals(TSRMLS_D) /* {{{ */
                PS(http_session_vars) = NULL;
        }
        /* Do NOT destroy PS(mod_user_names) here! */
-       if (PS(mod_data)) {
+       if (PS(mod_data) || PS(mod_user_implemented)) {
                zend_try {
                        PS(mod)->s_close(&PS(mod_data) TSRMLS_CC);
                } zend_end_try();
@@ -472,7 +477,7 @@ static void php_session_save_current_state(TSRMLS_D) /* {{{ */
        int ret = FAILURE;
 
        IF_SESSION_VARS() {
-               if (PS(mod_data)) {
+               if (PS(mod_data) || PS(mod_user_implemented)) {
                        char *val;
                        int vallen;
 
@@ -494,7 +499,7 @@ static void php_session_save_current_state(TSRMLS_D) /* {{{ */
                }
        }
 
-       if (PS(mod_data)) {
+       if (PS(mod_data) || PS(mod_user_implemented)) {
                PS(mod)->s_close(&PS(mod_data) TSRMLS_CC);
        }
 }
@@ -526,6 +531,8 @@ static PHP_INI_MH(OnUpdateSaveHandler) /* {{{ */
                }
                return FAILURE;
        }
+
+       PS(default_mod) = PS(mod);
        PS(mod) = tmp;
 
        return SUCCESS;
@@ -1420,7 +1427,7 @@ PHPAPI void php_session_start(TSRMLS_D) /* {{{ */
 
        php_session_cache_limiter(TSRMLS_C);
 
-       if (PS(mod_data) && PS(gc_probability) > 0) {
+       if ((PS(mod_data) || PS(mod_user_implemented)) && PS(gc_probability) > 0) {
                int nrdels = -1;
 
                nrand = (int) ((float) PS(gc_divisor) * php_combined_lcg(TSRMLS_C));
@@ -1555,7 +1562,7 @@ static PHP_FUNCTION(session_module_name)
                        zval_dtor(return_value);
                        RETURN_FALSE;
                }
-               if (PS(mod_data)) {
+               if (PS(mod_data) || PS(mod_user_implemented)) {
                        PS(mod)->s_close(&PS(mod_data) TSRMLS_CC);
                }
                PS(mod_data) = NULL;
@@ -1577,14 +1584,86 @@ static PHP_FUNCTION(session_set_save_handler)
                RETURN_FALSE;
        }
 
-       if (argc != 6) {
+       if (argc != 1 && argc != 2 && argc != 6) {
                WRONG_PARAM_COUNT;
        }
 
+       if (argc <= 2) {
+               zval *obj = NULL, *callback = NULL;
+               zend_uint func_name_len;
+               char *func_name;
+               HashPosition pos;
+               zend_function *default_mptr, *current_mptr;
+               ulong func_index;
+               php_shutdown_function_entry shutdown_function_entry;
+               zend_bool register_shutdown = 1;
+
+               if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|b", &obj, php_session_class_entry, &register_shutdown) == FAILURE) {
+                       return;
+               }
+
+               /* Find implemented methods */
+               zend_hash_internal_pointer_reset_ex(&php_session_class_entry->function_table, &pos);
+               i = 0;
+               while (zend_hash_get_current_data_ex(&php_session_class_entry->function_table, (void **) &default_mptr, &pos) == SUCCESS) {
+                       zend_hash_get_current_key_ex(&php_session_class_entry->function_table, &func_name, &func_name_len, &func_index, 0, &pos);
+
+                       if (zend_hash_find(&Z_OBJCE_P(obj)->function_table, func_name, func_name_len, (void **)&current_mptr) == SUCCESS) {
+                               if (PS(mod_user_names).names[i] != NULL) {
+                                       zval_ptr_dtor(&PS(mod_user_names).names[i]);
+                               }
+
+                               MAKE_STD_ZVAL(callback);
+                               array_init_size(callback, 2);
+                               Z_ADDREF_P(obj);
+                               add_next_index_zval(callback, obj);
+                               add_next_index_stringl(callback, func_name, func_name_len - 1, 1);
+                               PS(mod_user_names).names[i] = callback;
+                       } else {
+                               php_error_docref(NULL TSRMLS_CC, E_ERROR, "Session handler's function table is corrupt");
+                               RETURN_FALSE;
+                       }
+
+                       zend_hash_move_forward_ex(&php_session_class_entry->function_table, &pos);
+                       ++i;
+               }
+
+               if (register_shutdown) {
+                       /* create shutdown function */
+                       shutdown_function_entry.arg_count = 1;
+                       shutdown_function_entry.arguments = (zval **) safe_emalloc(sizeof(zval *), 1, 0);
+
+                       MAKE_STD_ZVAL(callback);
+                       ZVAL_STRING(callback, "session_register_shutdown", 1);
+                       shutdown_function_entry.arguments[0] = callback;
+
+                       /* add shutdown function, removing the old one if it exists */
+                       if (!register_user_shutdown_function("session_shutdown", &shutdown_function_entry)) {
+                               zval_ptr_dtor(&callback);
+                               efree(shutdown_function_entry.arguments);
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to register session shutdown function");
+                               RETURN_FALSE;
+                       }
+               } else {
+                       /* remove shutdown function */
+                       remove_user_shutdown_function("session_shutdown");
+               }
+
+               PS(mod_user_implemented) = 1;
+               if (PS(mod) && PS(session_status) == php_session_none && PS(mod) != &ps_mod_user) {
+                       zend_alter_ini_entry("session.save_handler", sizeof("session.save_handler"), "user", sizeof("user")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
+               }
+
+               RETURN_TRUE;
+       }
+
        if (zend_parse_parameters(argc TSRMLS_CC, "+", &args, &num_args) == FAILURE) {
                return;
        }
 
+       /* remove shutdown function */
+       remove_user_shutdown_function("session_shutdown");
+
        for (i = 0; i < 6; i++) {
                if (!zend_is_callable(*args[i], 0, &name TSRMLS_CC)) {
                        efree(args);
@@ -1594,8 +1673,12 @@ static PHP_FUNCTION(session_set_save_handler)
                }
                efree(name);
        }
+       
+       PS(mod_user_implemented) = 1;
 
-       zend_alter_ini_entry("session.save_handler", sizeof("session.save_handler"), "user", sizeof("user")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
+       if (PS(mod) && PS(mod) != &ps_mod_user) {
+               zend_alter_ini_entry("session.save_handler", sizeof("session.save_handler"), "user", sizeof("user")-1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
+       }
 
        for (i = 0; i < 6; i++) {
                if (PS(mod_user_names).names[i] != NULL) {
@@ -1842,6 +1925,43 @@ static PHP_FUNCTION(session_status)
 }
 /* }}} */
 
+/* {{{ proto void session_register_shutdown(void)
+   Registers session_write_close() as a shutdown function */
+static PHP_FUNCTION(session_register_shutdown)
+{
+       php_shutdown_function_entry shutdown_function_entry;
+       zval *callback;
+
+       /* This function is registered itself as a shutdown function by
+        * session_set_save_handler($obj). The reason we now register another
+        * shutdown function is in case the user registered their own shutdown
+        * function after calling session_set_save_handler(), which expects
+        * the session still to be available.
+        */
+
+       shutdown_function_entry.arg_count = 1;
+       shutdown_function_entry.arguments = (zval **) safe_emalloc(sizeof(zval *), 1, 0);
+
+       MAKE_STD_ZVAL(callback);
+       ZVAL_STRING(callback, "session_write_close", 1);
+       shutdown_function_entry.arguments[0] = callback;
+
+       if (!append_user_shutdown_function(shutdown_function_entry)) {
+               zval_ptr_dtor(&callback);
+               efree(shutdown_function_entry.arguments);
+
+               /* Unable to register shutdown function, presumably because of lack
+                * of memory, so flush the session now. It would be done in rshutdown
+                * anyway but the handler will have had it's dtor called by then.
+                * If the user does have a later shutdown function which needs the
+                * session then tough luck.
+                */
+               php_session_flush(TSRMLS_C);
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to register session flush function");
+       }
+}
+/* }}} */
+
 /* {{{ arginfo */
 ZEND_BEGIN_ARG_INFO_EX(arginfo_session_name, 0, 0, 0)
        ZEND_ARG_INFO(0, name)
@@ -1894,6 +2014,31 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_session_set_cookie_params, 0, 0, 1)
        ZEND_ARG_INFO(0, secure)
        ZEND_ARG_INFO(0, httponly)
 ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_session_class_open, 0)
+       ZEND_ARG_INFO(0, save_path)
+       ZEND_ARG_INFO(0, session_name)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_session_class_close, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_session_class_read, 0)
+       ZEND_ARG_INFO(0, key)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_session_class_write, 0)
+       ZEND_ARG_INFO(0, key)
+       ZEND_ARG_INFO(0, val)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_session_class_destroy, 0)
+       ZEND_ARG_INFO(0, key)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO(arginfo_session_class_gc, 0)
+       ZEND_ARG_INFO(0, maxlifetime)
+ZEND_END_ARG_INFO()
 /* }}} */
 
 /* {{{ session_functions[]
@@ -1916,11 +2061,25 @@ static const zend_function_entry session_functions[] = {
        PHP_FE(session_get_cookie_params, arginfo_session_void)
        PHP_FE(session_write_close,       arginfo_session_void)
        PHP_FE(session_status,            arginfo_session_void)
+       PHP_FE(session_register_shutdown, arginfo_session_void)
        PHP_FALIAS(session_commit, session_write_close, arginfo_session_void)
        PHP_FE_END
 };
 /* }}} */
 
+/* {{{ session class functions[]
+ */
+static const zend_function_entry php_session_class_functions[] = {
+       PHP_ME(SessionHandler, open, arginfo_session_class_open, ZEND_ACC_PUBLIC)
+       PHP_ME(SessionHandler, close, arginfo_session_class_close, ZEND_ACC_PUBLIC)
+       PHP_ME(SessionHandler, read, arginfo_session_class_read, ZEND_ACC_PUBLIC)
+       PHP_ME(SessionHandler, write, arginfo_session_class_write, ZEND_ACC_PUBLIC)
+       PHP_ME(SessionHandler, destroy, arginfo_session_class_destroy, ZEND_ACC_PUBLIC)
+       PHP_ME(SessionHandler, gc, arginfo_session_class_gc, ZEND_ACC_PUBLIC)
+       { NULL, NULL, NULL }
+};
+/* }}} */
+
 /* ********************************
    * Module Setup and Destruction *
    ******************************** */
@@ -1996,6 +2155,9 @@ static PHP_GINIT_FUNCTION(ps) /* {{{ */
        ps_globals->serializer = NULL;
        ps_globals->mod_data = NULL;
        ps_globals->session_status = php_session_none;
+       ps_globals->default_mod = NULL;
+       ps_globals->mod_user_implemented = 0;
+       ps_globals->mod_user_is_open = 0;
        for (i = 0; i < 6; i++) {
                ps_globals->mod_user_names.names[i] = NULL;
        }
@@ -2005,6 +2167,8 @@ static PHP_GINIT_FUNCTION(ps) /* {{{ */
 
 static PHP_MINIT_FUNCTION(session) /* {{{ */
 {
+       zend_class_entry ce;
+
        zend_register_auto_global("_SESSION", sizeof("_SESSION")-1, 0, NULL TSRMLS_CC);
 
        PS(module_number) = module_number; /* if we really need this var we need to init it in zts mode as well! */
@@ -2018,6 +2182,10 @@ static PHP_MINIT_FUNCTION(session) /* {{{ */
        php_session_rfc1867_orig_callback = php_rfc1867_callback;
        php_rfc1867_callback = php_session_rfc1867_callback;
 
+       /* Register base class */
+       INIT_CLASS_ENTRY(ce, PS_CLASS_NAME, php_session_class_functions);
+       php_session_class_entry = zend_register_internal_class(&ce TSRMLS_CC);
+
        REGISTER_LONG_CONSTANT("PHP_SESSION_DISABLED", php_session_disabled, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("PHP_SESSION_NONE", php_session_none, CONST_CS | CONST_PERSISTENT);
        REGISTER_LONG_CONSTANT("PHP_SESSION_ACTIVE", php_session_active, CONST_CS | CONST_PERSISTENT);
diff --git a/ext/session/tests/session_set_save_handler_class_001.phpt b/ext/session/tests/session_set_save_handler_class_001.phpt
new file mode 100644 (file)
index 0000000..667456c
--- /dev/null
@@ -0,0 +1,68 @@
+--TEST--
+Test session_set_save_handler() : basic class wrapping existing handler
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : basic class wrapping existing handler ***\n";
+
+class MySession extends SessionHandler {
+       public $i = 0;
+       public function open($path, $name) {
+               ++$this->i;
+               echo 'Open ', session_id(), "\n";
+               return parent::open($path, $name);
+       }
+       public function read($key) {
+               ++$this->i;
+               echo 'Read ', session_id(), "\n";
+               return parent::read($key);
+       }
+}
+
+$oldHandler = ini_get('session.save_handler');
+$handler = new MySession;
+session_set_save_handler($handler);
+session_start();
+
+var_dump(session_id(), $oldHandler, ini_get('session.save_handler'), $handler->i, $_SESSION);
+
+$_SESSION['foo'] = "hello";
+
+session_write_close();
+session_unset();
+
+session_start();
+var_dump($_SESSION);
+
+session_write_close();
+session_unset();
+
+--EXPECTF--
+*** Testing session_set_save_handler() : basic class wrapping existing handler ***
+Open 
+Read %x
+string(32) "%x"
+string(5) "files"
+string(4) "user"
+int(2)
+array(0) {
+}
+Open %x
+Read %x
+array(1) {
+  ["foo"]=>
+  string(5) "hello"
+}
diff --git a/ext/session/tests/session_set_save_handler_class_002.phpt b/ext/session/tests/session_set_save_handler_class_002.phpt
new file mode 100644 (file)
index 0000000..e907b21
--- /dev/null
@@ -0,0 +1,113 @@
+--TEST--
+Test session_set_save_handler() : full handler implementation
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : full handler implementation ***\n";
+
+class MySession2 extends SessionHandler {
+       public $path;
+
+       public function open($path, $name) {
+               if (!$path) {
+                       $path = '/tmp';
+               }
+               $this->path = $path . '/u_sess_' . $name;
+               return true;
+       }
+
+       public function close() {
+               return true;
+       }
+
+       public function read($id) {
+               return @file_get_contents($this->path . $id);
+       }
+
+       public function write($id, $data) {
+               return file_put_contents($this->path . $id, $data);
+       }
+
+       public function destroy($id) {
+               @unlink($this->path . $id);
+       }
+
+       public function gc($maxlifetime) {
+               foreach (glob($this->path . '*') as $filename) {
+                       if (filemtime($filename) + $maxlifetime < time()) {
+                               @unlink($filename);
+                       }
+               }
+               return true;
+       }
+}
+
+$handler = new MySession2;
+session_set_save_handler(array($handler, 'open'), array($handler, 'close'),
+       array($handler, 'read'), array($handler, 'write'), array($handler, 'destroy'), array($handler, 'gc'));
+session_start();
+
+$_SESSION['foo'] = "hello";
+
+var_dump(session_id(), ini_get('session.save_handler'), $_SESSION);
+
+session_write_close();
+session_unset();
+
+session_start();
+var_dump($_SESSION);
+
+session_write_close();
+session_unset();
+
+session_set_save_handler($handler);
+session_start();
+
+$_SESSION['foo'] = "hello";
+
+var_dump(session_id(), ini_get('session.save_handler'), $_SESSION);
+
+session_write_close();
+session_unset();
+
+session_start();
+var_dump($_SESSION);
+
+session_write_close();
+session_unset();
+
+--EXPECTF--
+*** Testing session_set_save_handler() : full handler implementation ***
+string(32) "%x"
+string(4) "user"
+array(1) {
+  ["foo"]=>
+  string(5) "hello"
+}
+array(1) {
+  ["foo"]=>
+  string(5) "hello"
+}
+string(32) "%x"
+string(4) "user"
+array(1) {
+  ["foo"]=>
+  string(5) "hello"
+}
+array(1) {
+  ["foo"]=>
+  string(5) "hello"
+}
diff --git a/ext/session/tests/session_set_save_handler_class_003.phpt b/ext/session/tests/session_set_save_handler_class_003.phpt
new file mode 100644 (file)
index 0000000..858a2e2
--- /dev/null
@@ -0,0 +1,78 @@
+--TEST--
+Test session_set_save_handler() : inheritance
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : inheritance ***\n";
+
+class MySession3 extends SessionHandler {
+       public $i = 0;
+       public function open($path, $name) {
+               ++$this->i;
+               return parent::open($path, $name);
+       }
+       public function read($key) {
+               ++$this->i;
+               return parent::read($key);
+       }
+}
+
+class MySession4 extends MySession3 {
+       public function write($id, $data) {
+               $this->i = "hai";
+               return parent::write($id, $data);
+       }
+}
+
+$handler = new MySession3;
+session_set_save_handler($handler);
+session_start();
+
+$_SESSION['foo'] = "hello";
+
+session_write_close();
+session_unset();
+
+session_start();
+
+var_dump($_SESSION, $handler->i);
+
+session_write_close();
+session_unset();
+
+$handler = new MySession4;
+session_set_save_handler($handler);
+
+session_start();
+
+session_write_close();
+session_unset();
+
+var_dump(session_id(), $_SESSION, $handler->i);
+
+--EXPECTF--
+*** Testing session_set_save_handler() : inheritance ***
+array(1) {
+  ["foo"]=>
+  string(5) "hello"
+}
+int(4)
+string(32) "%x"
+array(1) {
+  ["foo"]=>
+  string(5) "hello"
+}
+string(3) "hai"
diff --git a/ext/session/tests/session_set_save_handler_class_004.phpt b/ext/session/tests/session_set_save_handler_class_004.phpt
new file mode 100644 (file)
index 0000000..b9a5f45
--- /dev/null
@@ -0,0 +1,48 @@
+--TEST--
+Test session_set_save_handler() : default object
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : default object ***\n";
+
+session_set_save_handler(new SessionHandler);
+session_start();
+
+$_SESSION['foo'] = "hello";
+
+var_dump(session_id(), ini_get('session.save_handler'), $_SESSION);
+
+session_write_close();
+session_unset();
+session_start();
+
+var_dump($_SESSION);
+
+session_write_close();
+session_unset();
+
+--EXPECTF--
+*** Testing session_set_save_handler() : default object ***
+string(32) "%x"
+string(4) "user"
+array(1) {
+  ["foo"]=>
+  string(5) "hello"
+}
+array(1) {
+  ["foo"]=>
+  string(5) "hello"
+}
diff --git a/ext/session/tests/session_set_save_handler_class_005.phpt b/ext/session/tests/session_set_save_handler_class_005.phpt
new file mode 100644 (file)
index 0000000..4a7a54d
--- /dev/null
@@ -0,0 +1,54 @@
+--TEST--
+Test session_set_save_handler() : incomplete implementation
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : incomplete implementation ***\n";
+
+class MySession6 extends SessionHandler {
+       public function open($path, $name) {
+               // don't call parent
+               return true;
+       }
+
+       public function read($id) {
+               // should error because parent::open hasn't been called
+               return parent::read($id);
+       }
+}
+
+$handler = new MySession6;
+session_set_save_handler($handler);
+session_start();
+
+var_dump(session_id(), ini_get('session.save_handler'), $_SESSION);
+
+session_write_close();
+session_unset();
+
+
+--EXPECTF--
+*** Testing session_set_save_handler() : incomplete implementation ***
+
+Warning: SessionHandler::read(): Parent session handler is not open in %s/session_set_save_handler_class_005.php on line %d
+string(32) "%x"
+string(4) "user"
+array(0) {
+}
+
+Warning: SessionHandler::write(): Parent session handler is not open in %s/session_set_save_handler_class_005.php on line %d
+
+Warning: SessionHandler::close(): Parent session handler is not open in %s/session_set_save_handler_class_005.php on line %d
diff --git a/ext/session/tests/session_set_save_handler_class_006.phpt b/ext/session/tests/session_set_save_handler_class_006.phpt
new file mode 100644 (file)
index 0000000..5830b6d
--- /dev/null
@@ -0,0 +1,54 @@
+--TEST--
+Test session_set_save_handler() : using objects in close
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : using objects in close ***\n";
+
+class MySession7_Foo {
+       public $state = 'ok';
+       function __destruct() {
+               $this->state = 'destroyed';
+       }
+}
+
+class MySession7 extends SessionHandler {
+       public $foo;
+       public function close() {
+               var_dump($this->foo);
+               @var_dump($GLOBALS['bar']);
+               return parent::close();
+       }
+}
+
+$bar = new MySession7_Foo;
+$handler = new MySession7;
+$handler->foo = new MySession7_Foo;
+session_set_save_handler($handler);
+session_start();
+
+ob_end_flush();
+?>
+--EXPECTF--
+*** Testing session_set_save_handler() : using objects in close ***
+object(MySession7_Foo)#%d (%d) {
+  ["state"]=>
+  string(2) "ok"
+}
+object(MySession7_Foo)#%d (%d) {
+  ["state"]=>
+  string(2) "ok"
+}
diff --git a/ext/session/tests/session_set_save_handler_class_007.phpt b/ext/session/tests/session_set_save_handler_class_007.phpt
new file mode 100644 (file)
index 0000000..8d71ce4
--- /dev/null
@@ -0,0 +1,74 @@
+--TEST--
+Test session_set_save_handler() : manual shutdown, reopen
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : manual shutdown, reopen ***\n";
+
+class MySession extends SessionHandler {
+       public $num;
+       public function __construct($num) {
+               $this->num = $num;
+               echo "(#$this->num) constructor called\n";
+       }
+       public function __destruct() {
+               echo "(#$this->num) destructor called\n";
+       }
+       public function finish() {
+               $id = session_id();
+               echo "(#$this->num) finish called $id\n";
+               session_write_close();
+       }
+       public function write($id, $data) {
+               echo "(#$this->num) writing $id = $data\n";
+               return parent::write($id, $data);
+       }
+       public function close() {
+               $id = session_id();
+               echo "(#$this->num) closing $id\n";
+               return parent::close();
+       }
+}
+
+$handler = new MySession(1);
+session_set_save_handler($handler);
+session_start();
+
+$_SESSION['foo'] = 'bar';
+
+// explicit close
+$handler->finish();
+
+$handler = new MySession(2);
+session_set_save_handler($handler);
+session_start();
+
+// implicit close (called by shutdown function)
+echo "done\n";
+ob_end_flush();
+?>
+--EXPECTF--
+*** Testing session_set_save_handler() : manual shutdown, reopen ***
+(#1) constructor called
+(#1) finish called %x
+(#1) writing %x = foo|s:3:"bar";
+(#1) closing %x
+(#2) constructor called
+(#1) destructor called
+done
+(#2) writing %x = foo|s:3:"bar";
+(#2) closing %x
+(#2) destructor called
diff --git a/ext/session/tests/session_set_save_handler_class_008.phpt b/ext/session/tests/session_set_save_handler_class_008.phpt
new file mode 100644 (file)
index 0000000..7995920
--- /dev/null
@@ -0,0 +1,65 @@
+--TEST--
+Test session_set_save_handler() : manual shutdown
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : manual shutdown ***\n";
+
+class MySession extends SessionHandler {
+       public $num;
+       public function __construct($num) {
+               $this->num = $num;
+               echo "(#$this->num) constructor called\n";
+       }
+       public function __destruct() {
+               echo "(#$this->num) destructor called\n";
+       }
+       public function finish() {
+               $id = session_id();
+               echo "(#$this->num) finish called $id\n";
+               session_write_close();
+       }
+       public function write($id, $data) {
+               echo "(#$this->num) writing $id = $data\n";
+               return parent::write($id, $data);
+       }
+       public function close() {
+               $id = session_id();
+               echo "(#$this->num) closing $id\n";
+               return parent::close();
+       }
+}
+
+$handler = new MySession(1);
+session_set_save_handler($handler);
+session_start();
+
+$_SESSION['foo'] = 'bar';
+
+// explicit close
+$handler->finish();
+
+echo "done\n";
+ob_end_flush();
+?>
+--EXPECTF--
+*** Testing session_set_save_handler() : manual shutdown ***
+(#1) constructor called
+(#1) finish called %x
+(#1) writing %x = foo|s:3:"bar";
+(#1) closing %x
+done
+(#1) destructor called
diff --git a/ext/session/tests/session_set_save_handler_class_009.phpt b/ext/session/tests/session_set_save_handler_class_009.phpt
new file mode 100644 (file)
index 0000000..3378744
--- /dev/null
@@ -0,0 +1,62 @@
+--TEST--
+Test session_set_save_handler() : implicit shutdown
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : implicit shutdown ***\n";
+
+class MySession extends SessionHandler {
+       public $num;
+       public function __construct($num) {
+               $this->num = $num;
+               echo "(#$this->num) constructor called\n";
+       }
+       public function __destruct() {
+               echo "(#$this->num) destructor called\n";
+       }
+       public function finish() {
+               $id = session_id();
+               echo "(#$this->num) finish called $id\n";
+               $this->shutdown();
+       }
+       public function write($id, $data) {
+               echo "(#$this->num) writing $id = $data\n";
+               return parent::write($id, $data);
+       }
+       public function close() {
+               $id = session_id();
+               echo "(#$this->num) closing $id\n";
+               return parent::close();
+       }
+}
+
+$handler = new MySession(1);
+session_set_save_handler($handler);
+session_start();
+
+$_SESSION['foo'] = 'bar';
+
+// implicit close
+echo "done\n";
+ob_end_flush();
+?>
+--EXPECTF--
+*** Testing session_set_save_handler() : implicit shutdown ***
+(#1) constructor called
+done
+(#1) writing %x = foo|s:3:"bar";
+(#1) closing %x
+(#1) destructor called
diff --git a/ext/session/tests/session_set_save_handler_class_010.phpt b/ext/session/tests/session_set_save_handler_class_010.phpt
new file mode 100644 (file)
index 0000000..e60134f
--- /dev/null
@@ -0,0 +1,63 @@
+--TEST--
+Test session_set_save_handler() : manual shutdown function
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : manual shutdown function ***\n";
+
+class MySession extends SessionHandler {
+       public $num;
+       public function __construct($num) {
+               $this->num = $num;
+               echo "(#$this->num) constructor called\n";
+       }
+       public function __destruct() {
+               echo "(#$this->num) destructor called\n";
+       }
+       public function finish() {
+               $id = session_id();
+               echo "(#$this->num) finish called $id\n";
+               session_write_close();
+       }
+       public function write($id, $data) {
+               echo "(#$this->num) writing $id = $data\n";
+               return parent::write($id, $data);
+       }
+       public function close() {
+               $id = session_id();
+               echo "(#$this->num) closing $id\n";
+               return parent::close();
+       }
+}
+
+$handler = new MySession(1);
+session_set_save_handler($handler, false);
+register_shutdown_function(array($handler, 'finish'));
+session_start();
+
+$_SESSION['foo'] = 'bar';
+
+echo "done\n";
+ob_end_flush();
+?>
+--EXPECTF--
+*** Testing session_set_save_handler() : manual shutdown function ***
+(#1) constructor called
+done
+(#1) finish called %x
+(#1) writing %x = foo|s:3:"bar";
+(#1) closing %x
+(#1) destructor called
diff --git a/ext/session/tests/session_set_save_handler_class_011.phpt b/ext/session/tests/session_set_save_handler_class_011.phpt
new file mode 100644 (file)
index 0000000..7fa3657
--- /dev/null
@@ -0,0 +1,66 @@
+--TEST--
+Test session_set_save_handler() : shutdown failure
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : shutdown failure ***\n";
+
+class MySession extends SessionHandler {
+       public $num;
+       public $destroyed = false;
+       public function __construct($num) {
+               $this->num = $num;
+               echo "(#$this->num) constructor called\n";
+       }
+       public function __destruct() {
+               echo "(#$this->num) destructor called\n";
+               $this->destroyed = true;
+       }
+       public function write($id, $data) {
+               if ($this->destroyed) {
+                       echo "(#$this->num) destroyed, cannot write\n";
+               } else {
+                       echo "(#$this->num) writing $id = $data\n";
+               }
+               return parent::write($id, $data);
+       }
+       public function close() {
+               $id = session_id();
+               if ($this->destroyed) {
+                       echo "(#$this->num) destroyed, cannot write\n";
+               } else {
+                       echo "(#$this->num) closing $id\n";
+               }
+               return parent::close();
+       }
+}
+
+$handler = new MySession(1);
+session_set_save_handler($handler, false);
+session_start();
+
+$_SESSION['foo'] = 'bar';
+
+echo "done\n";
+ob_end_flush();
+?>
+--EXPECTF--
+*** Testing session_set_save_handler() : shutdown failure ***
+(#1) constructor called
+done
+(#1) destructor called
+(#1) destroyed, cannot write
+(#1) destroyed, cannot write
diff --git a/ext/session/tests/session_set_save_handler_class_012.phpt b/ext/session/tests/session_set_save_handler_class_012.phpt
new file mode 100644 (file)
index 0000000..f7f50d2
--- /dev/null
@@ -0,0 +1,59 @@
+--TEST--
+Test session_set_save_handler() : incorrect arguments for existing handler open
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : incorrect arguments for existing handler open ***\n";
+
+class MySession extends SessionHandler {
+       public $i = 0;
+       public function open($path, $name) {
+               ++$this->i;
+               echo 'Open ', session_id(), "\n";
+               return parent::open();
+       }
+       public function read($key) {
+               ++$this->i;
+               echo 'Read ', session_id(), "\n";
+               return parent::read($key);
+       }
+}
+
+$oldHandler = ini_get('session.save_handler');
+$handler = new MySession;
+session_set_save_handler($handler);
+session_start();
+
+var_dump(session_id(), $oldHandler, ini_get('session.save_handler'), $handler->i, $_SESSION);
+
+--EXPECTF--
+*** Testing session_set_save_handler() : incorrect arguments for existing handler open ***
+Open 
+
+Warning: SessionHandler::open() expects exactly 2 parameters, 0 given in %s on line %d
+Read %x
+
+Warning: SessionHandler::read(): Parent session handler is not open in %s on line %d
+string(32) "%x"
+string(5) "files"
+string(4) "user"
+int(2)
+array(0) {
+}
+
+Warning: Unknown: Parent session handler is not open in Unknown on line 0
+
+Warning: Unknown: Parent session handler is not open in Unknown on line 0
diff --git a/ext/session/tests/session_set_save_handler_class_013.phpt b/ext/session/tests/session_set_save_handler_class_013.phpt
new file mode 100644 (file)
index 0000000..f54aec4
--- /dev/null
@@ -0,0 +1,56 @@
+--TEST--
+Test session_set_save_handler() : incorrect arguments for existing handler close
+--INI--
+session.save_handler=files
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : incorrect arguments for existing handler close ***\n";
+
+class MySession extends SessionHandler {
+       public $i = 0;
+       public function open($path, $name) {
+               ++$this->i;
+               echo 'Open ', session_id(), "\n";
+               return parent::open($path, $name);
+       }
+       public function read($key) {
+               ++$this->i;
+               echo 'Read ', session_id(), "\n";
+               return parent::read($key);
+       }
+       public function close() {
+               return parent::close(false);
+       }
+}
+
+$oldHandler = ini_get('session.save_handler');
+$handler = new MySession;
+session_set_save_handler($handler);
+session_start();
+
+var_dump(session_id(), $oldHandler, ini_get('session.save_handler'), $handler->i, $_SESSION);
+
+--EXPECTF--
+*** Testing session_set_save_handler() : incorrect arguments for existing handler close ***
+Open 
+Read %x
+string(32) "%x"
+string(5) "files"
+string(4) "user"
+int(2)
+array(0) {
+}
+
+Warning: SessionHandler::close() expects exactly 0 parameters, 1 given in %s on line %d
diff --git a/ext/session/tests/session_set_save_handler_class_014.phpt b/ext/session/tests/session_set_save_handler_class_014.phpt
new file mode 100644 (file)
index 0000000..31b56c5
--- /dev/null
@@ -0,0 +1,32 @@
+--TEST--
+Test session_set_save_handler() : calling default handler when save_handler=user
+--INI--
+session.save_handler=user
+session.name=PHPSESSID
+--SKIPIF--
+<?php include('skipif.inc'); ?>
+--FILE--
+<?php
+
+ob_start();
+
+/* 
+ * Prototype : bool session_set_save_handler(SessionHandler $handler [, bool $register_shutdown_function = true])
+ * Description : Sets user-level session storage functions
+ * Source code : ext/session/session.c 
+ */
+
+echo "*** Testing session_set_save_handler() : calling default handler when save_handler=user ***\n";
+
+$oldHandler = ini_get('session.save_handler');
+$handler = new SessionHandler;
+session_set_save_handler($handler);
+
+session_start();
+
+--EXPECTF--
+*** Testing session_set_save_handler() : calling default handler when save_handler=user ***
+
+Fatal error: SessionHandler::open(): Called default SessionHandler but session.save_handler is user in Unknown on line %d
+
+Fatal error: Unknown: Called default SessionHandler but session.save_handler is user in Unknown on line %d
index e793e3ca93511aa7beadb44d9a1e5bd869fdd506..b8b53ddea3a60ee7a6d814106413df27790ee2e9 100644 (file)
@@ -118,11 +118,6 @@ PHPAPI php_basic_globals basic_globals;
 
 static zend_class_entry *incomplete_class_entry = NULL;
 
-typedef struct _php_shutdown_function_entry {
-       zval **arguments;
-       int arg_count;
-} php_shutdown_function_entry;
-
 typedef struct _user_tick_function_entry {
        zval **arguments;
        int arg_count;
@@ -5078,6 +5073,38 @@ PHP_FUNCTION(register_shutdown_function)
 }
 /* }}} */
 
+PHPAPI zend_bool register_user_shutdown_function(char *function_name, php_shutdown_function_entry *shutdown_function_entry) /* {{{ */
+{
+       if (!BG(user_shutdown_function_names)) {
+               ALLOC_HASHTABLE(BG(user_shutdown_function_names));
+               zend_hash_init(BG(user_shutdown_function_names), 0, NULL, (void (*)(void *)) user_shutdown_function_dtor, 0);
+       }
+
+       return zend_hash_update(BG(user_shutdown_function_names), function_name, sizeof(function_name), shutdown_function_entry, sizeof(php_shutdown_function_entry), NULL) != FAILURE;
+}
+/* }}} */
+
+PHPAPI zend_bool remove_user_shutdown_function(char *function_name) /* {{{ */
+{
+       if (BG(user_shutdown_function_names)) {
+               return zend_hash_del_key_or_index(BG(user_shutdown_function_names), function_name, sizeof(function_name), 0, HASH_DEL_KEY) != FAILURE;
+       }
+
+       return 0;
+}
+/* }}} */
+
+PHPAPI zend_bool append_user_shutdown_function(php_shutdown_function_entry shutdown_function_entry) /* {{{ */
+{
+       if (!BG(user_shutdown_function_names)) {
+               ALLOC_HASHTABLE(BG(user_shutdown_function_names));
+               zend_hash_init(BG(user_shutdown_function_names), 0, NULL, (void (*)(void *)) user_shutdown_function_dtor, 0);
+       }
+
+       return zend_hash_next_index_insert(BG(user_shutdown_function_names), &shutdown_function_entry, sizeof(php_shutdown_function_entry), NULL) != FAILURE;
+}
+/* }}} */
+
 ZEND_API void php_get_highlight_struct(zend_syntax_highlighter_ini *syntax_highlighter_ini) /* {{{ */
 {
        syntax_highlighter_ini->highlight_comment = INI_STR("highlight.comment");
index 4435f4bc4c75ac90a5d1192cdf80ce9eaedfded6..32b6d513172c3da5fd9b021503710aa7de7cbbfa 100644 (file)
@@ -251,4 +251,13 @@ typedef struct {
 PHPAPI double php_get_nan(void);
 PHPAPI double php_get_inf(void);
 
+typedef struct _php_shutdown_function_entry {
+       zval **arguments;
+       int arg_count;
+} php_shutdown_function_entry;
+
+PHPAPI extern zend_bool register_user_shutdown_function(char *function_name, php_shutdown_function_entry *shutdown_function_entry);
+PHPAPI extern zend_bool remove_user_shutdown_function(char *function_name);
+PHPAPI extern zend_bool append_user_shutdown_function(php_shutdown_function_entry shutdown_function_entry);
+
 #endif /* BASIC_FUNCTIONS_H */