diff --git a/php_phongo.c b/php_phongo.c index 74ba970c3..1fb6c3312 100644 --- a/php_phongo.c +++ b/php_phongo.c @@ -181,9 +181,49 @@ void phongo_throw_exception(php_phongo_error_domain_t domain TSRMLS_DC, const ch va_end(args); } -void phongo_throw_exception_from_bson_error_t(bson_error_t* error TSRMLS_DC) +static void phongo_exception_add_error_labels(bson_t* reply TSRMLS_DC) { - zend_throw_exception(phongo_exception_from_mongoc_domain(error->domain, error->code), error->message, error->code TSRMLS_CC); + bson_iter_t iter; + + if (!reply) { + return; + } + + if (bson_iter_init_find(&iter, reply, "errorLabels")) { + bson_iter_t error_labels; +#if PHP_VERSION_ID >= 70000 + zval labels; + + array_init(&labels); +#else + zval* labels = NULL; + + ALLOC_INIT_ZVAL(labels); + array_init(labels); +#endif + + bson_iter_recurse(&iter, &error_labels); + while (bson_iter_next(&error_labels)) { + if (BSON_ITER_HOLDS_UTF8(&error_labels)) { + const char* error_label; + uint32_t error_label_len; + + error_label = bson_iter_utf8(&error_labels, &error_label_len); +#if PHP_VERSION_ID >= 70000 + ADD_NEXT_INDEX_STRINGL(&labels, error_label, error_label_len); +#else + ADD_NEXT_INDEX_STRINGL(labels, error_label, error_label_len); +#endif + } + } + +#if PHP_VERSION_ID >= 70000 + phongo_add_exception_prop(ZEND_STRL("errorLabels"), &labels); +#else + phongo_add_exception_prop(ZEND_STRL("errorLabels"), labels TSRMLS_CC); +#endif + zval_ptr_dtor(&labels); + } } void phongo_throw_exception_from_bson_error_and_reply_t(bson_error_t* error, bson_t* reply TSRMLS_DC) @@ -192,7 +232,7 @@ void phongo_throw_exception_from_bson_error_and_reply_t(bson_error_t* error, bso * may use CommandException and report the result document for the * failed command. For BC, ExceededTimeLimit errors will continue to use * ExcecutionTimeoutException and omit the result document. */ - if ((error->domain == MONGOC_ERROR_SERVER && error->code != PHONGO_SERVER_ERROR_EXCEEDED_TIME_LIMIT) || error->domain == MONGOC_ERROR_WRITE_CONCERN) { + if (reply && ((error->domain == MONGOC_ERROR_SERVER && error->code != PHONGO_SERVER_ERROR_EXCEEDED_TIME_LIMIT) || error->domain == MONGOC_ERROR_WRITE_CONCERN)) { #if PHP_VERSION_ID >= 70000 zval zv; #else @@ -209,8 +249,14 @@ void phongo_throw_exception_from_bson_error_and_reply_t(bson_error_t* error, bso #endif zval_ptr_dtor(&zv); } else { - phongo_throw_exception_from_bson_error_t(error TSRMLS_CC); + zend_throw_exception(phongo_exception_from_mongoc_domain(error->domain, error->code), error->message, error->code TSRMLS_CC); } + phongo_exception_add_error_labels(reply TSRMLS_CC); +} + +void phongo_throw_exception_from_bson_error_t(bson_error_t* error TSRMLS_DC) +{ + phongo_throw_exception_from_bson_error_and_reply_t(error, NULL TSRMLS_CC); } static void php_phongo_log(mongoc_log_level_t log_level, const char* log_domain, const char* message, void* user_data) diff --git a/src/MongoDB/Exception/RuntimeException.c b/src/MongoDB/Exception/RuntimeException.c index 3d408c24d..a6691344e 100644 --- a/src/MongoDB/Exception/RuntimeException.c +++ b/src/MongoDB/Exception/RuntimeException.c @@ -23,12 +23,88 @@ #include "phongo_compat.h" #include "php_phongo.h" +#include "php_array_api.h" zend_class_entry* php_phongo_runtimeexception_ce; +static bool php_phongo_has_string_array_element(zval* labels, char* label TSRMLS_DC) +{ + HashTable* ht_data; + + if (Z_TYPE_P(labels) != IS_ARRAY) { + return false; + } + + ht_data = HASH_OF(labels); + +#if PHP_VERSION_ID >= 70000 + { + zval* z_label; + + ZEND_HASH_FOREACH_VAL(ht_data, z_label) + { + if ((Z_TYPE_P(z_label) == IS_STRING) && (strcmp(Z_STRVAL_P(z_label), label) == 0)) { + return true; + } + } + ZEND_HASH_FOREACH_END(); + } +#else + { + HashPosition pos; + zval** z_label; + + for ( + zend_hash_internal_pointer_reset_ex(ht_data, &pos); + zend_hash_get_current_data_ex(ht_data, (void**) &z_label, &pos) == SUCCESS; + zend_hash_move_forward_ex(ht_data, &pos)) { + + if (Z_TYPE_PP(z_label) == IS_STRING) { + if (strcmp(Z_STRVAL_PP(z_label), label) == 0) { + return true; + } + } + } + } +#endif + + return false; +} + +/* {{{ proto bool MongoDB\Driver\Exception\RuntimeException::hasErrorLabel(string $label) + Returns whether a specific error label has been set */ +static PHP_METHOD(RuntimeException, hasErrorLabel) +{ + char* label; + phongo_zpp_char_len label_len; + zval* error_labels; +#if PHP_VERSION_ID >= 70000 + zval rv; +#endif + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &label, &label_len) == FAILURE) { + return; + } + +#if PHP_VERSION_ID >= 70000 + error_labels = zend_read_property(php_phongo_runtimeexception_ce, getThis(), ZEND_STRL("errorLabels"), 0, &rv TSRMLS_CC); +#else + error_labels = zend_read_property(php_phongo_runtimeexception_ce, getThis(), ZEND_STRL("errorLabels"), 0 TSRMLS_CC); +#endif + + RETURN_BOOL(php_phongo_has_string_array_element(error_labels, label TSRMLS_CC)); +} /* }}} */ + +ZEND_BEGIN_ARG_INFO_EX(ai_RuntimeException_hasErrorLabel, 0, 0, 1) + ZEND_ARG_INFO(0, label) +ZEND_END_ARG_INFO() + /* {{{ MongoDB\Driver\Exception\RuntimeException function entries */ static zend_function_entry php_phongo_runtimeexception_me[] = { + /* clang-format off */ + PHP_ME(RuntimeException, hasErrorLabel, ai_RuntimeException_hasErrorLabel, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL) PHP_FE_END + /* clang-format on */ }; /* }}} */ @@ -43,6 +119,8 @@ void php_phongo_runtimeexception_init_ce(INIT_FUNC_ARGS) /* {{{ */ php_phongo_runtimeexception_ce = zend_register_internal_class_ex(&ce, spl_ce_RuntimeException, NULL TSRMLS_CC); #endif zend_class_implements(php_phongo_runtimeexception_ce TSRMLS_CC, 1, php_phongo_exception_ce); + + zend_declare_property_null(php_phongo_runtimeexception_ce, ZEND_STRL("errorLabels"), ZEND_ACC_PROTECTED TSRMLS_CC); } /* }}} */ /* diff --git a/tests/apm/overview.phpt b/tests/apm/overview.phpt index 52dfa3b57..465dccca6 100644 --- a/tests/apm/overview.phpt +++ b/tests/apm/overview.phpt @@ -88,6 +88,8 @@ object(MongoDB\Driver\Monitoring\CommandFailedEvent)#%d (%d) { %a ["previous":"Exception":private]=> NULL + ["errorLabels":protected]=> + NULL } ["operationId"]=> string(%d) "%s" diff --git a/tests/exception/bulkwriteexception-haserrorlabel-001.phpt b/tests/exception/bulkwriteexception-haserrorlabel-001.phpt new file mode 100644 index 000000000..6a7a4b390 --- /dev/null +++ b/tests/exception/bulkwriteexception-haserrorlabel-001.phpt @@ -0,0 +1,24 @@ +--TEST-- +MongoDB\Driver\Exception\BulkWriteException::hasErrorLabel() +--FILE-- +getProperty('errorLabels'); +$resultDocumentProperty->setAccessible(true); +$resultDocumentProperty->setValue($exception, $labels); + +var_dump($exception->hasErrorLabel('foo')); +var_dump($exception->hasErrorLabel('bar')); + +?> +===DONE=== + +--EXPECT-- +bool(true) +bool(false) +===DONE=== diff --git a/tests/exception/bulkwriteexception-haserrorlabel_error-001.phpt b/tests/exception/bulkwriteexception-haserrorlabel_error-001.phpt new file mode 100644 index 000000000..229f50b1f --- /dev/null +++ b/tests/exception/bulkwriteexception-haserrorlabel_error-001.phpt @@ -0,0 +1,22 @@ +--TEST-- +MongoDB\Driver\Exception\BulkWriteException::hasErrorLabel() with non-array values +--FILE-- +getProperty('errorLabels'); +$resultDocumentProperty->setAccessible(true); +$resultDocumentProperty->setValue($exception, $labels); + +var_dump($exception->hasErrorLabel('bar')); + +?> +===DONE=== + +--EXPECT-- +bool(false) +===DONE=== diff --git a/tests/exception/commandexception-haserrorlabel-001.phpt b/tests/exception/commandexception-haserrorlabel-001.phpt new file mode 100644 index 000000000..0a3389662 --- /dev/null +++ b/tests/exception/commandexception-haserrorlabel-001.phpt @@ -0,0 +1,24 @@ +--TEST-- +MongoDB\Driver\Exception\CommandException::hasErrorLabel() +--FILE-- +getProperty('errorLabels'); +$resultDocumentProperty->setAccessible(true); +$resultDocumentProperty->setValue($exception, $labels); + +var_dump($exception->hasErrorLabel('foo')); +var_dump($exception->hasErrorLabel('bar')); + +?> +===DONE=== + +--EXPECT-- +bool(true) +bool(false) +===DONE=== diff --git a/tests/exception/commandexception-haserrorlabel_error-001.phpt b/tests/exception/commandexception-haserrorlabel_error-001.phpt new file mode 100644 index 000000000..ea3241480 --- /dev/null +++ b/tests/exception/commandexception-haserrorlabel_error-001.phpt @@ -0,0 +1,22 @@ +--TEST-- +MongoDB\Driver\Exception\CommandException::hasErrorLabel() with non-array values +--FILE-- +getProperty('errorLabels'); +$resultDocumentProperty->setAccessible(true); +$resultDocumentProperty->setValue($exception, $labels); + +var_dump($exception->hasErrorLabel('bar')); + +?> +===DONE=== + +--EXPECT-- +bool(false) +===DONE=== diff --git a/tests/exception/runtimeexception-haserrorlabel-001.phpt b/tests/exception/runtimeexception-haserrorlabel-001.phpt new file mode 100644 index 000000000..c9283045d --- /dev/null +++ b/tests/exception/runtimeexception-haserrorlabel-001.phpt @@ -0,0 +1,24 @@ +--TEST-- +MongoDB\Driver\Exception\RuntimeException::hasErrorLabel() +--FILE-- +getProperty('errorLabels'); +$resultDocumentProperty->setAccessible(true); +$resultDocumentProperty->setValue($exception, $labels); + +var_dump($exception->hasErrorLabel('foo')); +var_dump($exception->hasErrorLabel('bar')); + +?> +===DONE=== + +--EXPECT-- +bool(true) +bool(false) +===DONE=== diff --git a/tests/exception/runtimeexception-haserrorlabel_error-001.phpt b/tests/exception/runtimeexception-haserrorlabel_error-001.phpt new file mode 100644 index 000000000..d9de912d0 --- /dev/null +++ b/tests/exception/runtimeexception-haserrorlabel_error-001.phpt @@ -0,0 +1,22 @@ +--TEST-- +MongoDB\Driver\Exception\RuntimeException::hasErrorLabel() with non-array values +--FILE-- +getProperty('errorLabels'); +$resultDocumentProperty->setAccessible(true); +$resultDocumentProperty->setValue($exception, $labels); + +var_dump($exception->hasErrorLabel('bar')); + +?> +===DONE=== + +--EXPECT-- +bool(false) +===DONE=== diff --git a/tests/session/transaction-integration-002.phpt b/tests/session/transaction-integration-002.phpt index 3ea2073e1..b892ccf95 100644 --- a/tests/session/transaction-integration-002.phpt +++ b/tests/session/transaction-integration-002.phpt @@ -61,9 +61,7 @@ try { ] ); $manager->executeCommand(DATABASE_NAME, $cmd, ['session' => $sessionB]); } catch (MongoDB\Driver\Exception\CommandException $e) { - $rd = $e->getResultDocument(); - - echo (isset($rd->errorLabels) && in_array("TransientTransactionError", $rd->errorLabels)) ? + echo $e->hasErrorLabel('TransientTransactionError') ? "found a TransientTransactionError" : "did NOT get a TransientTransactionError", "\n"; } ?>