// Protocol Buffers - Google's data interchange format // Copyright 2008 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "array.h" #include #include #include // This is not self-contained: it must be after other Zend includes. #include #include "arena.h" #include "convert.h" #include "def.h" #include "php-upb.h" #include "protobuf.h" static void RepeatedFieldIter_make(zval *val, zval *repeated_field); // ----------------------------------------------------------------------------- // RepeatedField // ----------------------------------------------------------------------------- typedef struct { zend_object std; zval arena; upb_array *array; upb_fieldtype_t type; const Descriptor* desc; // When values are messages. } RepeatedField; zend_class_entry *RepeatedField_class_entry; static zend_object_handlers RepeatedField_object_handlers; // PHP Object Handlers ///////////////////////////////////////////////////////// /** * RepeatedField_create() * * PHP class entry function to allocate and initialize a new RepeatedField * object. */ static zend_object* RepeatedField_create(zend_class_entry *class_type) { RepeatedField *intern = emalloc(sizeof(RepeatedField)); zend_object_std_init(&intern->std, class_type); intern->std.handlers = &RepeatedField_object_handlers; Arena_Init(&intern->arena); intern->array = NULL; intern->desc = NULL; // Skip object_properties_init(), we don't allow derived classes. return &intern->std; } /** * RepeatedField_dtor() * * Object handler to destroy a RepeatedField. This releases all resources * associated with the message. Note that it is possible to access a destroyed * object from PHP in rare cases. */ static void RepeatedField_destructor(zend_object* obj) { RepeatedField* intern = (RepeatedField*)obj; ObjCache_Delete(intern->array); zval_ptr_dtor(&intern->arena); zend_object_std_dtor(&intern->std); } static HashTable *RepeatedField_GetProperties(zval *object TSRMLS_DC) { return NULL; // We do not have a properties table. } static zval *RepeatedField_GetPropertyPtrPtr(zval *object, zval *member, int type, void **cache_slot) { return NULL; // We don't offer direct references to our properties. } // C Functions from array.h //////////////////////////////////////////////////// // These are documented in the header file. void RepeatedField_GetPhpWrapper(zval *val, upb_array *arr, const upb_fielddef *f, zval *arena) { if (!arr) { ZVAL_NULL(val); return; } if (!ObjCache_Get(arr, val)) { RepeatedField *intern = emalloc(sizeof(RepeatedField)); zend_object_std_init(&intern->std, RepeatedField_class_entry); intern->std.handlers = &RepeatedField_object_handlers; ZVAL_COPY(&intern->arena, arena); intern->array = arr; intern->type = upb_fielddef_type(f); intern->desc = Descriptor_GetFromFieldDef(f); // Skip object_properties_init(), we don't allow derived classes. ObjCache_Add(intern->array, &intern->std); ZVAL_OBJ(val, &intern->std); } } upb_array *RepeatedField_GetUpbArray(zval *val, const upb_fielddef *f, upb_arena *arena) { if (Z_ISREF_P(val)) { ZVAL_DEREF(val); } if (Z_TYPE_P(val) == IS_ARRAY) { // Auto-construct, eg. [1, 2, 3] -> upb_array([1, 2, 3]). upb_array *arr = upb_array_new(arena, upb_fielddef_type(f)); HashTable *table = HASH_OF(val); HashPosition pos; upb_fieldtype_t type = upb_fielddef_type(f); const Descriptor *desc = Descriptor_GetFromFieldDef(f); zend_hash_internal_pointer_reset_ex(table, &pos); while (true) { zval *zv = zend_hash_get_current_data_ex(table, &pos); upb_msgval val; if (!zv) return arr; if (!Convert_PhpToUpbAutoWrap(zv, &val, type, desc, arena)) { return NULL; } upb_array_append(arr, val, arena); zend_hash_move_forward_ex(table, &pos); } } else if (Z_TYPE_P(val) == IS_OBJECT && Z_OBJCE_P(val) == RepeatedField_class_entry) { // Unwrap existing RepeatedField object to get the upb_array* inside. RepeatedField *intern = (RepeatedField*)Z_OBJ_P(val); const Descriptor *desc = Descriptor_GetFromFieldDef(f); if (intern->type != upb_fielddef_type(f) || intern->desc != desc) { php_error_docref(NULL, E_USER_ERROR, "Wrong type for this repeated field."); } upb_arena_fuse(arena, Arena_Get(&intern->arena)); return intern->array; } else { php_error_docref(NULL, E_USER_ERROR, "Must be a repeated field"); return NULL; } } // RepeatedField PHP methods /////////////////////////////////////////////////// /** * RepeatedField::__construct() * * Constructs an instance of RepeatedField. * @param long Type of the stored element. * @param string Message/Enum class. */ PHP_METHOD(RepeatedField, __construct) { RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis()); upb_arena *arena = Arena_Get(&intern->arena); zend_long type; zend_class_entry* klass = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|C", &type, &klass) != SUCCESS) { return; } intern->type = pbphp_dtype_to_type(type); intern->desc = Descriptor_GetFromClassEntry(klass); if (intern->type == UPB_TYPE_MESSAGE && klass == NULL) { php_error_docref(NULL, E_USER_ERROR, "Message/enum type must have concrete class."); return; } intern->array = upb_array_new(arena, intern->type); ObjCache_Add(intern->array, &intern->std); } /** * RepeatedField::append() * * Append element to the end of the repeated field. * @param object The element to be added. */ PHP_METHOD(RepeatedField, append) { RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis()); upb_arena *arena = Arena_Get(&intern->arena); zval *php_val; upb_msgval msgval; if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &php_val) != SUCCESS || !Convert_PhpToUpb(php_val, &msgval, intern->type, intern->desc, arena)) { return; } upb_array_append(intern->array, msgval, arena); } /** * RepeatedField::offsetExists() * * Implements the ArrayAccess interface. Invoked when PHP code calls: * * isset($arr[$idx]); * empty($arr[$idx]); * * @param long The index to be checked. * @return bool True if the element at the given index exists. */ PHP_METHOD(RepeatedField, offsetExists) { RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis()); zend_long index; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { return; } RETURN_BOOL(index >= 0 && index < upb_array_size(intern->array)); } /** * RepeatedField::offsetGet() * * Implements the ArrayAccess interface. Invoked when PHP code calls: * * $x = $arr[$idx]; * * @param long The index of the element to be fetched. * @return object The stored element at given index. * @exception Invalid type for index. * @exception Non-existing index. */ PHP_METHOD(RepeatedField, offsetGet) { RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis()); zend_long index; upb_msgval msgval; zval ret; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { return; } if (index < 0 || index >= upb_array_size(intern->array)) { zend_error(E_USER_ERROR, "Element at %ld doesn't exist.\n", index); return; } msgval = upb_array_get(intern->array, index); Convert_UpbToPhp(msgval, &ret, intern->type, intern->desc, &intern->arena); RETURN_ZVAL(&ret, 0, 1); } /** * RepeatedField::offsetSet() * * Implements the ArrayAccess interface. Invoked when PHP code calls: * * $arr[$idx] = $x; * $arr []= $x; // Append * * @param long The index of the element to be assigned. * @param object The element to be assigned. * @exception Invalid type for index. * @exception Non-existing index. * @exception Incorrect type of the element. */ PHP_METHOD(RepeatedField, offsetSet) { RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis()); upb_arena *arena = Arena_Get(&intern->arena); size_t size = upb_array_size(intern->array); zval *offset, *val; int64_t index; upb_msgval msgval; if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &offset, &val) != SUCCESS) { return; } if (Z_TYPE_P(offset) == IS_NULL) { index = size; } else if (!Convert_PhpToInt64(offset, &index)) { return; } if (!Convert_PhpToUpb(val, &msgval, intern->type, intern->desc, arena)) { return; } if (index > size) { zend_error(E_USER_ERROR, "Element at index %ld doesn't exist.\n", index); } else if (index == size) { upb_array_append(intern->array, msgval, Arena_Get(&intern->arena)); } else { upb_array_set(intern->array, index, msgval); } } /** * RepeatedField::offsetUnset() * * Implements the ArrayAccess interface. Invoked when PHP code calls: * * unset($arr[$idx]); * * @param long The index of the element to be removed. * @exception Invalid type for index. * @exception The element to be removed is not at the end of the RepeatedField. */ PHP_METHOD(RepeatedField, offsetUnset) { RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis()); zend_long index; zend_long size = upb_array_size(intern->array); // Only the element at the end of the array can be removed. if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) != SUCCESS) { return; } if (size == 0 || index != size - 1) { php_error_docref(NULL, E_USER_ERROR, "Cannot remove element at %ld.\n", index); return; } upb_array_resize(intern->array, size - 1, Arena_Get(&intern->arena)); } /** * RepeatedField::count() * * Implements the Countable interface. Invoked when PHP code calls: * * $len = count($arr); * Return the number of stored elements. * This will also be called for: count($arr) * @return long The number of stored elements. */ PHP_METHOD(RepeatedField, count) { RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis()); if (zend_parse_parameters_none() == FAILURE) { return; } RETURN_LONG(upb_array_size(intern->array)); } /** * RepeatedField::getIterator() * * Implements the IteratorAggregate interface. Invoked when PHP code calls: * * foreach ($arr) {} * * @return object Beginning iterator. */ PHP_METHOD(RepeatedField, getIterator) { zval ret; RepeatedFieldIter_make(&ret, getThis()); RETURN_ZVAL(&ret, 0, 1); } ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetGet, 0, 0, 1) ZEND_ARG_INFO(0, index) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetSet, 0, 0, 2) ZEND_ARG_INFO(0, index) ZEND_ARG_INFO(0, newval) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_void, 0) ZEND_END_ARG_INFO() static zend_function_entry repeated_field_methods[] = { PHP_ME(RepeatedField, __construct, NULL, ZEND_ACC_PUBLIC) PHP_ME(RepeatedField, append, NULL, ZEND_ACC_PUBLIC) PHP_ME(RepeatedField, offsetExists, arginfo_offsetGet, ZEND_ACC_PUBLIC) PHP_ME(RepeatedField, offsetGet, arginfo_offsetGet, ZEND_ACC_PUBLIC) PHP_ME(RepeatedField, offsetSet, arginfo_offsetSet, ZEND_ACC_PUBLIC) PHP_ME(RepeatedField, offsetUnset, arginfo_offsetGet, ZEND_ACC_PUBLIC) PHP_ME(RepeatedField, count, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RepeatedField, getIterator, arginfo_void, ZEND_ACC_PUBLIC) ZEND_FE_END }; // ----------------------------------------------------------------------------- // PHP RepeatedFieldIter // ----------------------------------------------------------------------------- typedef struct { zend_object std; zval repeated_field; zend_long position; } RepeatedFieldIter; zend_class_entry *RepeatedFieldIter_class_entry; static zend_object_handlers repeated_field_iter_object_handlers; /** * RepeatedFieldIter_create() * * PHP class entry function to allocate and initialize a new RepeatedFieldIter * object. */ zend_object* RepeatedFieldIter_create(zend_class_entry *class_type) { RepeatedFieldIter *intern = emalloc(sizeof(RepeatedFieldIter)); zend_object_std_init(&intern->std, class_type); intern->std.handlers = &repeated_field_iter_object_handlers; ZVAL_NULL(&intern->repeated_field); intern->position = 0; // Skip object_properties_init(), we don't allow derived classes. return &intern->std; } /** * RepeatedFieldIter_dtor() * * Object handler to destroy a RepeatedFieldIter. This releases all resources * associated with the message. Note that it is possible to access a destroyed * object from PHP in rare cases. */ static void RepeatedFieldIter_dtor(zend_object* obj) { RepeatedFieldIter* intern = (RepeatedFieldIter*)obj; zval_ptr_dtor(&intern->repeated_field); zend_object_std_dtor(&intern->std); } /** * RepeatedFieldIter_make() * * C function to create a RepeatedFieldIter. */ static void RepeatedFieldIter_make(zval *val, zval *repeated_field) { RepeatedFieldIter *iter; ZVAL_OBJ(val, RepeatedFieldIter_class_entry->create_object( RepeatedFieldIter_class_entry)); iter = (RepeatedFieldIter*)Z_OBJ_P(val); ZVAL_COPY(&iter->repeated_field, repeated_field); } /* * When a user writes: * * foreach($arr as $key => $val) {} * * PHP's iterator protocol is: * * $iter = $arr->getIterator(); * for ($iter->rewind(); $iter->valid(); $iter->next()) { * $key = $iter->key(); * $val = $iter->current(); * } */ /** * RepeatedFieldIter::rewind() * * Implements the Iterator interface. Sets the iterator to the first element. */ PHP_METHOD(RepeatedFieldIter, rewind) { RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis()); intern->position = 0; } /** * RepeatedFieldIter::current() * * Implements the Iterator interface. Returns the current value. */ PHP_METHOD(RepeatedFieldIter, current) { RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis()); RepeatedField *field = (RepeatedField*)Z_OBJ_P(&intern->repeated_field); upb_array *array = field->array; zend_long index = intern->position; upb_msgval msgval; zval ret; if (index < 0 || index >= upb_array_size(array)) { zend_error(E_USER_ERROR, "Element at %ld doesn't exist.\n", index); } msgval = upb_array_get(array, index); Convert_UpbToPhp(msgval, &ret, field->type, field->desc, &field->arena); RETURN_ZVAL(&ret, 0, 1); } /** * RepeatedFieldIter::key() * * Implements the Iterator interface. Returns the current key. */ PHP_METHOD(RepeatedFieldIter, key) { RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis()); RETURN_LONG(intern->position); } /** * RepeatedFieldIter::next() * * Implements the Iterator interface. Advances to the next element. */ PHP_METHOD(RepeatedFieldIter, next) { RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis()); ++intern->position; } /** * RepeatedFieldIter::valid() * * Implements the Iterator interface. Returns true if this is a valid element. */ PHP_METHOD(RepeatedFieldIter, valid) { RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis()); RepeatedField *field = (RepeatedField*)Z_OBJ_P(&intern->repeated_field); RETURN_BOOL(intern->position < upb_array_size(field->array)); } static zend_function_entry repeated_field_iter_methods[] = { PHP_ME(RepeatedFieldIter, rewind, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RepeatedFieldIter, current, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RepeatedFieldIter, key, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RepeatedFieldIter, next, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RepeatedFieldIter, valid, arginfo_void, ZEND_ACC_PUBLIC) ZEND_FE_END }; // ----------------------------------------------------------------------------- // Module init. // ----------------------------------------------------------------------------- /** * Array_ModuleInit() * * Called when the C extension is loaded to register all types. */ void Array_ModuleInit() { zend_class_entry tmp_ce; zend_object_handlers *h; // RepeatedField. INIT_CLASS_ENTRY(tmp_ce, "Google\\Protobuf\\Internal\\RepeatedField", repeated_field_methods); RepeatedField_class_entry = zend_register_internal_class(&tmp_ce); zend_class_implements(RepeatedField_class_entry, 3, spl_ce_ArrayAccess, zend_ce_aggregate, spl_ce_Countable); RepeatedField_class_entry->ce_flags |= ZEND_ACC_FINAL; RepeatedField_class_entry->create_object = RepeatedField_create; h = &RepeatedField_object_handlers; memcpy(h, &std_object_handlers, sizeof(zend_object_handlers)); h->dtor_obj = RepeatedField_destructor; h->get_properties = RepeatedField_GetProperties; h->get_property_ptr_ptr = RepeatedField_GetPropertyPtrPtr; // RepeatedFieldIter INIT_CLASS_ENTRY(tmp_ce, "Google\\Protobuf\\Internal\\RepeatedFieldIter", repeated_field_iter_methods); RepeatedFieldIter_class_entry = zend_register_internal_class(&tmp_ce); zend_class_implements(RepeatedFieldIter_class_entry, 1, zend_ce_iterator); RepeatedFieldIter_class_entry->ce_flags |= ZEND_ACC_FINAL; RepeatedFieldIter_class_entry->create_object = RepeatedFieldIter_create; h = &repeated_field_iter_object_handlers; memcpy(h, &std_object_handlers, sizeof(zend_object_handlers)); h->dtor_obj = RepeatedFieldIter_dtor; }