/* * Copyright 2014-present MongoDB, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php_phongo.h" #include "phongo_error.h" #include "phongo_bson_encode.h" #include "BSON/Document_arginfo.h" #include "BSON/Iterator.h" zend_class_entry* php_phongo_document_ce; /* Initialize the object from a HashTable and return whether it was successful. * An exception will be thrown on error. */ static bool php_phongo_document_init_from_hash(php_phongo_document_t* intern, HashTable* props) { zval* data; if ((data = zend_hash_str_find(props, "data", sizeof("data") - 1)) && Z_TYPE_P(data) == IS_STRING) { zend_string* decoded = php_base64_decode_str(Z_STR_P(data)); intern->bson = bson_new_from_data((const uint8_t*) ZSTR_VAL(decoded), ZSTR_LEN(decoded)); zend_string_free(decoded); if (intern->bson == NULL) { phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "%s initialization requires valid BSON", ZSTR_VAL(php_phongo_document_ce->name)); return false; } return true; } phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "%s initialization requires \"data\" string field", ZSTR_VAL(php_phongo_document_ce->name)); return false; } static HashTable* php_phongo_document_get_properties_hash(zend_object* object, bool is_temp, int size) { php_phongo_document_t* intern; HashTable* props; intern = Z_OBJ_DOCUMENT(object); PHONGO_GET_PROPERTY_HASH_INIT_PROPS(is_temp, intern, props, size); if (!intern->bson) { return props; } { zval data; ZVAL_STR(&data, php_base64_encode((const unsigned char*) bson_get_data(intern->bson), intern->bson->len)); zend_hash_str_update(props, "data", sizeof("data") - 1, &data); } return props; } PHONGO_DISABLED_CONSTRUCTOR(MongoDB_BSON_Document) static PHP_METHOD(MongoDB_BSON_Document, fromBSON) { zval zv; php_phongo_document_t* intern = NULL; zend_string* bson_string; const bson_t* bson; bson_reader_t* reader; bool eof = false; PHONGO_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(bson_string) PHONGO_PARSE_PARAMETERS_END(); reader = bson_reader_new_from_data((const uint8_t*) ZSTR_VAL(bson_string), ZSTR_LEN(bson_string)); if (!(bson = bson_reader_read(reader, NULL))) { phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Could not read document from BSON reader"); goto cleanup; } object_init_ex(&zv, php_phongo_document_ce); intern = Z_DOCUMENT_OBJ_P(&zv); intern->bson = bson_copy(bson); if (bson_reader_read(reader, &eof) || !eof) { phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "Reading document did not exhaust input buffer"); zval_ptr_dtor(&zv); goto cleanup; } RETVAL_ZVAL(&zv, 1, 1); cleanup: if (reader) { bson_reader_destroy(reader); } } static PHP_METHOD(MongoDB_BSON_Document, fromJSON) { zval zv; php_phongo_document_t* intern; zend_string* json; bson_t* bson; bson_error_t error; PHONGO_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(json) PHONGO_PARSE_PARAMETERS_END(); bson = bson_new_from_json((const uint8_t*) ZSTR_VAL(json), ZSTR_LEN(json), &error); if (!bson) { phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "%s", error.domain == BSON_ERROR_JSON ? error.message : "Error parsing JSON"); return; } object_init_ex(&zv, php_phongo_document_ce); intern = Z_DOCUMENT_OBJ_P(&zv); intern->bson = bson; RETURN_ZVAL(&zv, 1, 1); } static PHP_METHOD(MongoDB_BSON_Document, fromPHP) { zval zv; php_phongo_document_t* intern; zval* data; PHONGO_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY_OR_OBJECT(data) PHONGO_PARSE_PARAMETERS_END(); object_init_ex(&zv, php_phongo_document_ce); intern = Z_DOCUMENT_OBJ_P(&zv); intern->bson = bson_new(); // Explicitly allow constructing a Document from a PackedArray php_phongo_zval_to_bson(data, PHONGO_BSON_ALLOW_ROOT_ARRAY, intern->bson, NULL); RETURN_ZVAL(&zv, 1, 1); } static bool php_phongo_document_get(php_phongo_document_t* intern, char* key, size_t key_len, zval* return_value, bool null_if_missing) { bson_iter_t iter; if (!bson_iter_init(&iter, intern->bson)) { phongo_throw_exception(PHONGO_ERROR_RUNTIME, "Could not initialize BSON iterator"); return false; } if (!bson_iter_find_w_len(&iter, key, key_len)) { if (null_if_missing) { ZVAL_NULL(return_value); return true; } phongo_throw_exception(PHONGO_ERROR_RUNTIME, "Could not find key \"%s\" in BSON document", key); return false; } phongo_bson_value_to_zval(bson_iter_value(&iter), return_value); return true; } static bool php_phongo_document_get_by_zval(php_phongo_document_t* intern, zval* key, zval* return_value, bool null_if_missing) { if (Z_TYPE_P(key) != IS_STRING && Z_TYPE_P(key) != IS_LONG) { if (null_if_missing) { ZVAL_NULL(return_value); return true; } phongo_throw_exception(PHONGO_ERROR_RUNTIME, "Could not find key of type \"%s\" in BSON document", zend_zval_type_name(key)); return false; } zend_string* tmp_str; zend_string* str = zval_try_get_tmp_string(key, &tmp_str); if (!str) { // Exception already thrown return false; } if (!php_phongo_document_get(intern, ZSTR_VAL(str), ZSTR_LEN(str), return_value, null_if_missing)) { // Exception already thrown zend_tmp_string_release(tmp_str); return false; } zend_tmp_string_release(tmp_str); return true; } static PHP_METHOD(MongoDB_BSON_Document, get) { php_phongo_document_t* intern; char* key; size_t key_len; PHONGO_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(key, key_len) PHONGO_PARSE_PARAMETERS_END(); intern = Z_DOCUMENT_OBJ_P(getThis()); // May throw, in which case we do nothing php_phongo_document_get(intern, key, key_len, return_value, false); } static PHP_METHOD(MongoDB_BSON_Document, getIterator) { PHONGO_PARSE_PARAMETERS_NONE(); phongo_iterator_init(return_value, getThis()); } static bool php_phongo_document_has(php_phongo_document_t* intern, char* key, size_t key_len) { bson_iter_t iter; if (!bson_iter_init(&iter, intern->bson)) { phongo_throw_exception(PHONGO_ERROR_RUNTIME, "Could not initialize BSON iterator"); return false; } return bson_iter_find_w_len(&iter, key, key_len); } static bool php_phongo_document_has_by_zval(php_phongo_document_t* intern, zval* key) { if (Z_TYPE_P(key) != IS_STRING && Z_TYPE_P(key) != IS_LONG) { return false; } zend_string* tmp_str; zend_string* str = zval_try_get_tmp_string(key, &tmp_str); if (!str) { // Exception already thrown return false; } if (!php_phongo_document_has(intern, ZSTR_VAL(str), ZSTR_LEN(str))) { // Exception may be thrown if BSON iterator could not be initialized zend_tmp_string_release(tmp_str); return false; } zend_tmp_string_release(tmp_str); return true; } static PHP_METHOD(MongoDB_BSON_Document, has) { php_phongo_document_t* intern; char* key; size_t key_len; PHONGO_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STRING(key, key_len) PHONGO_PARSE_PARAMETERS_END(); intern = Z_DOCUMENT_OBJ_P(getThis()); RETURN_BOOL(php_phongo_document_has(intern, key, key_len)); } static PHP_METHOD(MongoDB_BSON_Document, toCanonicalExtendedJSON) { php_phongo_document_t* intern; PHONGO_PARSE_PARAMETERS_NONE(); intern = Z_DOCUMENT_OBJ_P(getThis()); php_phongo_bson_to_json(return_value, intern->bson, PHONGO_JSON_MODE_CANONICAL); } static PHP_METHOD(MongoDB_BSON_Document, toRelaxedExtendedJSON) { php_phongo_document_t* intern; PHONGO_PARSE_PARAMETERS_NONE(); intern = Z_DOCUMENT_OBJ_P(getThis()); php_phongo_bson_to_json(return_value, intern->bson, PHONGO_JSON_MODE_RELAXED); } static PHP_METHOD(MongoDB_BSON_Document, toPHP) { php_phongo_document_t* intern; zval* typemap = NULL; php_phongo_bson_state state; PHONGO_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL Z_PARAM_ARRAY(typemap) PHONGO_PARSE_PARAMETERS_END(); PHONGO_BSON_INIT_STATE(state); if (!php_phongo_bson_typemap_to_state(typemap, &state.map)) { return; } intern = Z_DOCUMENT_OBJ_P(getThis()); state.map.int64_as_object = true; if (!php_phongo_bson_to_zval_ex(intern->bson, &state)) { zval_ptr_dtor(&state.zchild); php_phongo_bson_typemap_dtor(&state.map); RETURN_NULL(); } php_phongo_bson_typemap_dtor(&state.map); RETURN_ZVAL(&state.zchild, 0, 1); } static PHP_METHOD(MongoDB_BSON_Document, offsetExists) { php_phongo_document_t* intern; zval* offset; PHONGO_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(offset) PHONGO_PARSE_PARAMETERS_END(); intern = Z_DOCUMENT_OBJ_P(getThis()); RETURN_BOOL(php_phongo_document_has_by_zval(intern, offset)); } static PHP_METHOD(MongoDB_BSON_Document, offsetGet) { php_phongo_document_t* intern; zval* offset; PHONGO_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ZVAL(offset) PHONGO_PARSE_PARAMETERS_END(); intern = Z_DOCUMENT_OBJ_P(getThis()); // May throw, in which case we do nothing php_phongo_document_get_by_zval(intern, offset, return_value, false); } static PHP_METHOD(MongoDB_BSON_Document, offsetSet) { phongo_throw_exception(PHONGO_ERROR_LOGIC, "Cannot write to %s property", ZSTR_VAL(php_phongo_document_ce->name)); } static PHP_METHOD(MongoDB_BSON_Document, offsetUnset) { phongo_throw_exception(PHONGO_ERROR_LOGIC, "Cannot unset %s property", ZSTR_VAL(php_phongo_document_ce->name)); } static PHP_METHOD(MongoDB_BSON_Document, __toString) { php_phongo_document_t* intern; PHONGO_PARSE_PARAMETERS_NONE(); intern = Z_DOCUMENT_OBJ_P(getThis()); RETVAL_STRINGL((const char*) bson_get_data(intern->bson), intern->bson->len); } static PHP_METHOD(MongoDB_BSON_Document, __set_state) { php_phongo_document_t* intern; HashTable* props; zval* array; PHONGO_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(array) PHONGO_PARSE_PARAMETERS_END(); object_init_ex(return_value, php_phongo_document_ce); intern = Z_DOCUMENT_OBJ_P(return_value); props = Z_ARRVAL_P(array); php_phongo_document_init_from_hash(intern, props); } static PHP_METHOD(MongoDB_BSON_Document, __serialize) { PHONGO_PARSE_PARAMETERS_NONE(); RETURN_ARR(php_phongo_document_get_properties_hash(Z_OBJ_P(getThis()), true, 1)); } static PHP_METHOD(MongoDB_BSON_Document, __unserialize) { zval* data; PHONGO_PARSE_PARAMETERS_START(1, 1) Z_PARAM_ARRAY(data) PHONGO_PARSE_PARAMETERS_END(); php_phongo_document_init_from_hash(Z_DOCUMENT_OBJ_P(getThis()), Z_ARRVAL_P(data)); } /* MongoDB\BSON\BSON object handlers */ static zend_object_handlers php_phongo_handler_document; static void php_phongo_document_free_object(zend_object* object) { php_phongo_document_t* intern = Z_OBJ_DOCUMENT(object); zend_object_std_dtor(&intern->std); if (intern->bson) { bson_destroy(intern->bson); } if (intern->properties) { zend_hash_destroy(intern->properties); FREE_HASHTABLE(intern->properties); } } static zend_object* php_phongo_document_create_object(zend_class_entry* class_type) { php_phongo_document_t* intern = zend_object_alloc(sizeof(php_phongo_document_t), class_type); zend_object_std_init(&intern->std, class_type); object_properties_init(&intern->std, class_type); intern->std.handlers = &php_phongo_handler_document; return &intern->std; } static zend_object* php_phongo_document_clone_object(zend_object* object) { php_phongo_document_t* intern; php_phongo_document_t* new_intern; zend_object* new_object; intern = Z_OBJ_DOCUMENT(object); new_object = php_phongo_document_create_object(object->ce); new_intern = Z_OBJ_DOCUMENT(new_object); zend_objects_clone_members(&new_intern->std, &intern->std); new_intern->bson = bson_copy(intern->bson); return new_object; } static int php_phongo_document_compare_objects(zval* o1, zval* o2) { php_phongo_document_t *intern1, *intern2; ZEND_COMPARE_OBJECTS_FALLBACK(o1, o2); intern1 = Z_DOCUMENT_OBJ_P(o1); intern2 = Z_DOCUMENT_OBJ_P(o2); return bson_compare(intern1->bson, intern2->bson); } static HashTable* php_phongo_document_get_debug_info(zend_object* object, int* is_temp) { php_phongo_document_t* intern; HashTable* props; *is_temp = 1; intern = Z_OBJ_DOCUMENT(object); /* This get_debug_info handler reports an additional property. This does not * conflict with other uses of php_phongo_document_get_properties_hash since * we always allocated a new HashTable with is_temp=true. */ props = php_phongo_document_get_properties_hash(object, true, 2); { php_phongo_bson_state state; PHONGO_BSON_INIT_STATE(state); state.map.array.type = PHONGO_TYPEMAP_BSON; state.map.document.type = PHONGO_TYPEMAP_BSON; if (!php_phongo_bson_to_zval_ex(intern->bson, &state)) { zval_ptr_dtor(&state.zchild); goto failure; } zend_hash_str_update(props, "value", sizeof("value") - 1, &state.zchild); } return props; failure: PHONGO_GET_PROPERTY_HASH_FREE_PROPS(is_temp, props); return NULL; } static HashTable* php_phongo_document_get_properties(zend_object* object) { return php_phongo_document_get_properties_hash(object, false, 1); } zval* php_phongo_document_read_property(zend_object* object, zend_string* member, int type, void** cache_slot, zval* rv) { php_phongo_document_t* intern = Z_OBJ_DOCUMENT(object); if (!php_phongo_document_get(intern, ZSTR_VAL(member), ZSTR_LEN(member), rv, type == BP_VAR_IS)) { // Exception already thrown return &EG(uninitialized_zval); } return rv; } zval* php_phongo_document_write_property(zend_object* object, zend_string* member, zval* value, void** cache_slot) { phongo_throw_exception(PHONGO_ERROR_LOGIC, "Cannot write to %s property", ZSTR_VAL(php_phongo_document_ce->name)); return value; } int php_phongo_document_has_property(zend_object* object, zend_string* member, int has_set_exists, void** cache_slot) { php_phongo_document_t* intern = Z_OBJ_DOCUMENT(object); return php_phongo_document_has(intern, ZSTR_VAL(member), ZSTR_LEN(member)); } void php_phongo_document_unset_property(zend_object* object, zend_string* member, void** cache_slot) { phongo_throw_exception(PHONGO_ERROR_LOGIC, "Cannot unset %s property", ZSTR_VAL(php_phongo_document_ce->name)); } zval* php_phongo_document_read_dimension(zend_object* object, zval* offset, int type, zval* rv) { php_phongo_document_t* intern = Z_OBJ_DOCUMENT(object); if (!php_phongo_document_get_by_zval(intern, offset, rv, type == BP_VAR_IS)) { // Exception already thrown return &EG(uninitialized_zval); } return rv; } void php_phongo_document_write_dimension(zend_object* object, zval* offset, zval* value) { phongo_throw_exception(PHONGO_ERROR_LOGIC, "Cannot write to %s property", ZSTR_VAL(php_phongo_document_ce->name)); } int php_phongo_document_has_dimension(zend_object* object, zval* member, int check_empty) { php_phongo_document_t* intern = Z_OBJ_DOCUMENT(object); return php_phongo_document_has_by_zval(intern, member); } void php_phongo_document_unset_dimension(zend_object* object, zval* offset) { phongo_throw_exception(PHONGO_ERROR_LOGIC, "Cannot unset %s property", ZSTR_VAL(php_phongo_document_ce->name)); } void php_phongo_document_init_ce(INIT_FUNC_ARGS) { php_phongo_document_ce = register_class_MongoDB_BSON_Document(zend_ce_aggregate, zend_ce_arrayaccess, php_phongo_type_ce, zend_ce_stringable); php_phongo_document_ce->create_object = php_phongo_document_create_object; memcpy(&php_phongo_handler_document, phongo_get_std_object_handlers(), sizeof(zend_object_handlers)); php_phongo_handler_document.compare = php_phongo_document_compare_objects; php_phongo_handler_document.clone_obj = php_phongo_document_clone_object; php_phongo_handler_document.get_debug_info = php_phongo_document_get_debug_info; php_phongo_handler_document.get_properties = php_phongo_document_get_properties; php_phongo_handler_document.free_obj = php_phongo_document_free_object; php_phongo_handler_document.read_property = php_phongo_document_read_property; php_phongo_handler_document.write_property = php_phongo_document_write_property; php_phongo_handler_document.has_property = php_phongo_document_has_property; php_phongo_handler_document.unset_property = php_phongo_document_unset_property; php_phongo_handler_document.read_dimension = php_phongo_document_read_dimension; php_phongo_handler_document.write_dimension = php_phongo_document_write_dimension; php_phongo_handler_document.has_dimension = php_phongo_document_has_dimension; php_phongo_handler_document.unset_dimension = php_phongo_document_unset_dimension; php_phongo_handler_document.offset = XtOffsetOf(php_phongo_document_t, std); } bool phongo_document_new(zval* object, bson_t* bson, bool copy) { php_phongo_document_t* intern; object_init_ex(object, php_phongo_document_ce); intern = Z_DOCUMENT_OBJ_P(object); intern->bson = copy ? bson_copy(bson) : bson; return true; }