Skip to content

PHPC-1459: Add serialization support for CursorId #1053

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 141 additions & 1 deletion src/MongoDB/CursorId.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@

#include <php.h>
#include <Zend/zend_interfaces.h>
#include <ext/standard/php_var.h>
#if PHP_VERSION_ID >= 70000
#include <zend_smart_str.h>
#else
#include <ext/standard/php_smart_str.h>
#endif

#ifdef HAVE_CONFIG_H
#include "config.h"
Expand All @@ -26,6 +32,48 @@

zend_class_entry* php_phongo_cursorid_ce;

/* Initialize the object from a numeric string and return whether it was
* successful. An exception will be thrown on error. */
static bool php_phongo_cursorid_init_from_string(php_phongo_cursorid_t* intern, const char* s_id, phongo_zpp_char_len s_id_len TSRMLS_DC) /* {{{ */
{
int64_t id;
char* endptr = NULL;

/* bson_ascii_strtoll() sets errno if conversion fails. If conversion
* succeeds, we still want to ensure that the entire string was parsed. */
id = bson_ascii_strtoll(s_id, &endptr, 10);

if (errno || (endptr && endptr != ((const char*) s_id + s_id_len))) {
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error parsing \"%s\" as 64-bit id for %s initialization", s_id, ZSTR_VAL(php_phongo_cursorid_ce->name));
return false;
}

intern->id = id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Contrasting this with Int64.c, I noticed that CursorId.c has a get_debug_info handler but no get_properties handler. Likewise, the CursorId struct doesn't have a separate initialized boolean to differentiate an empty struct from a populated one (since we don't have a pointer field to work with and prefer not to make assumptions based on any int fields).

Since the BSON classes and RC/RP/WC value objects have both handlers, would it make sense to have a new ticket to do the same for CursorId? Practically, this would just allow one to call var_export() on this object. I expect it'd just entail copying a bit more code from Int64.c into a separate PR. Thankfully this isn't something we need to bother documenting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return true;
} /* }}} */

/* Initialize the object from a HashTable and return whether it was successful.
* An exception will be thrown on error. */
static bool php_phongo_cursorid_init_from_hash(php_phongo_cursorid_t* intern, HashTable* props TSRMLS_DC) /* {{{ */
{
#if PHP_VERSION_ID >= 70000
zval* value;

if ((value = zend_hash_str_find(props, "id", sizeof("id") - 1)) && Z_TYPE_P(value) == IS_STRING) {
return php_phongo_cursorid_init_from_string(intern, Z_STRVAL_P(value), Z_STRLEN_P(value) TSRMLS_CC);
}
#else
zval** value;

if (zend_hash_find(props, "id", sizeof("id"), (void**) &value) == SUCCESS && Z_TYPE_PP(value) == IS_STRING) {
return php_phongo_cursorid_init_from_string(intern, Z_STRVAL_PP(value), Z_STRLEN_PP(value) TSRMLS_CC);
}
#endif

phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "%s initialization requires \"id\" string field", ZSTR_VAL(php_phongo_cursorid_ce->name));
return false;
} /* }}} */

/* {{{ proto string MongoDB\Driver\CursorId::__toString()
Returns the string representation of the CursorId */
static PHP_METHOD(CursorId, __toString)
Expand All @@ -45,13 +93,104 @@ static PHP_METHOD(CursorId, __toString)
efree(tmp);
} /* }}} */

/* {{{ proto string MongoDB\Driver\CursorId::serialize()
*/
static PHP_METHOD(CursorId, serialize)
{
php_phongo_cursorid_t* intern;
ZVAL_RETVAL_TYPE retval;
php_serialize_data_t var_hash;
smart_str buf = { 0 };
char s_id[24];
int s_id_len;

if (zend_parse_parameters_none() == FAILURE) {
return;
}

intern = Z_CURSORID_OBJ_P(getThis());

s_id_len = snprintf(s_id, sizeof(s_id), "%" PRId64, intern->id);

#if PHP_VERSION_ID >= 70000
array_init_size(&retval, 1);
ADD_ASSOC_STRINGL(&retval, "id", s_id, s_id_len);
#else
ALLOC_INIT_ZVAL(retval);
array_init_size(retval, 1);
ADD_ASSOC_STRINGL(retval, "id", s_id, s_id_len);
#endif

PHP_VAR_SERIALIZE_INIT(var_hash);
php_var_serialize(&buf, &retval, &var_hash TSRMLS_CC);
smart_str_0(&buf);
PHP_VAR_SERIALIZE_DESTROY(var_hash);

PHONGO_RETVAL_SMART_STR(buf);

smart_str_free(&buf);
zval_ptr_dtor(&retval);
} /* }}} */

/* {{{ proto void MongoDB\Driver\CursorId::unserialize(string $serialized)
*/
static PHP_METHOD(CursorId, unserialize)
{
php_phongo_cursorid_t* intern;
zend_error_handling error_handling;
char* serialized;
phongo_zpp_char_len serialized_len;
#if PHP_VERSION_ID >= 70000
zval props;
#else
zval* props;
#endif
php_unserialize_data_t var_hash;

intern = Z_CURSORID_OBJ_P(getThis());

zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling TSRMLS_CC);

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &serialized, &serialized_len) == FAILURE) {
zend_restore_error_handling(&error_handling TSRMLS_CC);
return;
}
zend_restore_error_handling(&error_handling TSRMLS_CC);

#if PHP_VERSION_ID < 70000
ALLOC_INIT_ZVAL(props);
#endif
PHP_VAR_UNSERIALIZE_INIT(var_hash);
if (!php_var_unserialize(&props, (const unsigned char**) &serialized, (unsigned char*) serialized + serialized_len, &var_hash TSRMLS_CC)) {
zval_ptr_dtor(&props);
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE TSRMLS_CC, "%s unserialization failed", ZSTR_VAL(php_phongo_cursorid_ce->name));

PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
return;
}
PHP_VAR_UNSERIALIZE_DESTROY(var_hash);

#if PHP_VERSION_ID >= 70000
php_phongo_cursorid_init_from_hash(intern, HASH_OF(&props) TSRMLS_CC);
#else
php_phongo_cursorid_init_from_hash(intern, HASH_OF(props) TSRMLS_CC);
#endif
zval_ptr_dtor(&props);
} /* }}} */

/* {{{ MongoDB\Driver\CursorId function entries */
ZEND_BEGIN_ARG_INFO_EX(ai_CursorId_unserialize, 0, 0, 1)
ZEND_ARG_INFO(0, serialized)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(ai_CursorId_void, 0, 0, 0)
ZEND_END_ARG_INFO()

static zend_function_entry php_phongo_cursorid_me[] = {
/* clang-format off */
PHP_ME(CursorId, __toString, ai_CursorId_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
PHP_ME(CursorId, serialize, ai_CursorId_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
PHP_ME(CursorId, unserialize, ai_CursorId_unserialize, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
ZEND_NAMED_ME(__construct, PHP_FN(MongoDB_disabled___construct), ai_CursorId_void, ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)
ZEND_NAMED_ME(__wakeup, PHP_FN(MongoDB_disabled___wakeup), ai_CursorId_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
PHP_FE_END
Expand Down Expand Up @@ -131,7 +270,8 @@ void php_phongo_cursorid_init_ce(INIT_FUNC_ARGS) /* {{{ */
php_phongo_cursorid_ce = zend_register_internal_class(&ce TSRMLS_CC);
php_phongo_cursorid_ce->create_object = php_phongo_cursorid_create_object;
PHONGO_CE_FINAL(php_phongo_cursorid_ce);
PHONGO_CE_DISABLE_SERIALIZATION(php_phongo_cursorid_ce);

zend_class_implements(php_phongo_cursorid_ce TSRMLS_CC, 1, zend_ce_serializable);

memcpy(&php_phongo_handler_cursorid, phongo_get_std_object_handlers(), sizeof(zend_object_handlers));
php_phongo_handler_cursorid.get_debug_info = php_phongo_cursorid_get_debug_info;
Expand Down
26 changes: 26 additions & 0 deletions tests/cursorid/cursorid-serialization-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
MongoDB\Driver\CursorID serialization
--FILE--
<?php

require_once __DIR__ . "/../utils/basic.inc";

$serialized = 'C:23:"MongoDB\Driver\CursorId":42:{a:1:{s:2:"id";s:19:"7250031947823432848";}}';

$cursorId = unserialize($serialized);

var_dump($cursorId);
var_dump($cursorId instanceof Serializable);
echo serialize($cursorId), "\n";

?>
===DONE===
<?php exit(0); ?>
--EXPECTF--
object(MongoDB\Driver\CursorId)#%d (%d) {
["id"]=>
%rint\(\d+\)|string\(\d+\) "\d+"%r
}
bool(true)
C:23:"MongoDB\Driver\CursorId":42:{a:1:{s:2:"id";s:19:"7250031947823432848";}}
===DONE===