Repository: kjdev/php-ext-brotli
Pull request #58: Develop
Author: login: kjdev
Created: 2025-04-22T00:47:31Z | Merged: 2025-04-22T02:15:06Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
	- Compression and decompression contexts are now represented as dedicated PHP objects (`Brotli\Compress\Context` and `Brotli\UnCompress\Context`) instead of generic resources.
	- Added comprehensive Brotli-related constants for easier configuration and usage.

- **Documentation**
	- Updated documentation to reflect the new object-based API and improved type clarity for all context-related functions.

- **Refactor**
	- Modernized the extension's API and internal logic to use PHP objects for context management, enhancing type safety and structure.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/README.md b/README.md
index 545cce8..ea6b27c 100644
--- a/README.md
+++ b/README.md
@@ -134,7 +134,7 @@ The original uncompressed data or FALSE on error.
 
 #### Description
 
-resource **brotli\_compress\_init** ( [ int _$quality_ = BROTLI\_COMPRESS\_LEVEL\_DEFAULT, int _$mode_ = BROTLI\_GENERIC ] )
+Brotli\Compress\Context **brotli\_compress\_init** ( [ int _$quality_ = BROTLI\_COMPRESS\_LEVEL\_DEFAULT, int _$mode_ = BROTLI\_GENERIC ] )
 
 Initialize an incremental compress context.
 
@@ -152,7 +152,7 @@ Initialize an incremental compress context.
 
 #### Return Values
 
-Returns a brotli context resource (brotli.state) on success,
+Returns a `Brotli\Compress\Context` instance on success,
 or FALSE on failure.
 
 ---
@@ -160,7 +160,7 @@ or FALSE on failure.
 
 #### Description
 
-string **brotli\_compress\_add** ( resource _$context_, string _$data_ [, _$mode_ = BROTLI\_FLUSH ] )
+string **brotli\_compress\_add** ( Brotli\Compress\Context _$context_, string _$data_ [, _$mode_ = BROTLI\_FLUSH ] )
 
 Incrementally compress data.
 
@@ -189,13 +189,13 @@ Returns a chunk of compressed data, or FALSE on failure.
 
 #### Description
 
-resource **brotli\_uncompress\_init** ( void )
+Brotli\UnCompress\Context **brotli\_uncompress\_init** ( void )
 
 Initialize an incremental uncompress context.
 
 #### Return Values
 
-Returns a brotli context resource (brotli.state) on success,
+Returns a `Brotli\UnCompress\Context` instance on success,
 or FALSE on failure.
 
 ---
@@ -203,7 +203,7 @@ or FALSE on failure.
 
 #### Description
 
-string **brotli\_uncompress\_add** ( resource _$context_, string _$data_ [, _$mode_ = BROTLI\_FLUSH ] )
+string **brotli\_uncompress\_add** ( Brotli\UnCompress\Context _$context_, string _$data_ [, _$mode_ = BROTLI\_FLUSH ] )
 
 Incrementally uncompress data.
 
@@ -235,9 +235,9 @@ Namespace Brotli;
 function compress( $data [, $quality = \\BROTLI\_COMPRESS\_LEVEL\_DEFAULT, $mode = \\BROTLI\_GENERIC ] )
 function uncompress( $data [, $length = 0 ] )
 function compress\_init( [ $quality = \\BROTLI\_COMPRESS\_LEVEL\_DEFAULT, $mode = \\BROTLI\_GENERIC ] )
-function compress\_add( resource $context, string $data [, $mode = \\BROTLI\_FLUSH] )
+function compress\_add( \\Brotli\\Compress\\Context $context, string $data [, $mode = \\BROTLI\_FLUSH] )
 function uncompress\_init()
-function uncompress\_add( resource $context, string $data [, $mode = \\BROTLI\_FLUSH] )
+function uncompress\_add( \\Brotli\\UnCompress\\Context $context, string $data [, $mode = \\BROTLI\_FLUSH] )
 ```
 
 alias functions..
diff --git a/brotli.c b/brotli.c
index 64322f5..81fc59d 100644
--- a/brotli.c
+++ b/brotli.c
@@ -12,10 +12,9 @@
 #include <ext/apcu/apc_serializer.h>
 #include <zend_smart_str.h>
 #endif
+#include <Zend/zend_interfaces.h>
 #include "php_brotli.h"
 
-int le_state;
-
 # pragma GCC diagnostic ignored "-Wpointer-sign"
 
 ZEND_DECLARE_MODULE_GLOBALS(brotli);
@@ -132,35 +131,6 @@ static int php_brotli_decoder_create(BrotliDecoderState **decoder)
     return SUCCESS;
 }
 
-static php_brotli_state_context* php_brotli_state_init(void)
-{
-    php_brotli_state_context *ctx
-        = (php_brotli_state_context *)ecalloc(1,
-                                              sizeof(php_brotli_state_context));
-    ctx->encoder = NULL;
-    ctx->decoder = NULL;
-    return ctx;
-}
-
-static void php_brotli_state_destroy(php_brotli_state_context *ctx)
-{
-    if (ctx->encoder) {
-        BrotliEncoderDestroyInstance(ctx->encoder);
-        ctx->encoder = NULL;
-    }
-    if (ctx->decoder) {
-        BrotliDecoderDestroyInstance(ctx->decoder);
-        ctx->decoder = NULL;
-    }
-    efree(ctx);
-}
-
-static void php_brotli_state_rsrc_dtor(zend_resource *res)
-{
-    php_brotli_state_context *ctx = zend_fetch_resource(res, NULL, le_state);
-    php_brotli_state_destroy(ctx);
-}
-
 #define PHP_BROTLI_OUTPUT_HANDLER "ob_brotli_handler"
 
 static int php_brotli_output_encoding(void)
@@ -859,6 +829,135 @@ php_stream_wrapper php_stream_brotli_wrapper = {
     0 /* is_url */
 };
 
+/* Brotli Compress/UnCompress Context */
+#define PHP_BROTLI_CONTEXT_OBJ_INIT_OF_CLASS(ce) \
+  php_brotli_context *ctx; \
+  object_init_ex(return_value, ce); \
+  ctx = php_brotli_context_from_obj(Z_OBJ_P(return_value)); \
+  ctx->state.encoder = NULL; \
+  ctx->state.decoder = NULL; \
+  ctx->available_in = 0; \
+  ctx->next_in = NULL; \
+  ctx->available_out = 0; \
+  ctx->next_out = NULL; \
+  ctx->output = NULL;
+
+static php_brotli_context *php_brotli_context_from_obj(zend_object *obj)
+{
+    return (php_brotli_context *)
+        ((char *)(obj) - XtOffsetOf(php_brotli_context, std));
+}
+
+static void php_brotli_context_free_obj(zend_object *object)
+{
+    php_brotli_context *intern = php_brotli_context_from_obj(object);
+    php_brotli_context_close(intern);
+    zend_object_std_dtor(&intern->std);
+}
+
+static zend_object *
+php_brotli_context_create_object(zend_class_entry *class_type,
+                                 zend_object_handlers *handlers)
+{
+    php_brotli_context *intern;
+#if PHP_VERSION_ID >= 80000
+    intern = zend_object_alloc(sizeof(php_brotli_context), class_type);
+#else
+    intern = ecalloc(1,
+                     sizeof(php_brotli_context)
+                     + zend_object_properties_size(class_type));
+#endif
+    zend_object_std_init(&intern->std, class_type);
+    object_properties_init(&intern->std, class_type);
+    intern->std.handlers = handlers;
+
+    return &intern->std;
+}
+
+/* Brotli Compress Context */
+zend_class_entry *php_brotli_compress_context_ce;
+static zend_object_handlers php_brotli_compress_context_object_handlers;
+
+static zend_object *
+php_brotli_compress_context_create_object(zend_class_entry *class_type)
+{
+    return php_brotli_context_create_object(
+        class_type,
+        &php_brotli_compress_context_object_handlers);
+}
+
+static zend_function *
+php_brotli_compress_context_get_constructor(zend_object *object)
+{
+    zend_throw_error(NULL,
+                     "Cannot directly construct Brotli\\Compress\\Context, "
+                     "use brotli_compress_init() instead");
+    return NULL;
+}
+
+static zend_class_entry *php_brotli_compress_context_register_class(void)
+{
+    zend_class_entry ce, *class_entry;
+
+    INIT_NS_CLASS_ENTRY(ce, "Brotli\\Compress", "Context", NULL);
+#if PHP_VERSION_ID >= 80000
+    class_entry = zend_register_internal_class_ex(&ce, NULL);
+#if PHP_VERSION_ID >= 80100
+    class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE;
+#else
+    class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES;
+#endif
+#else
+    ce.create_object = php_brotli_compress_context_create_object;
+    class_entry = zend_register_internal_class(&ce);
+    class_entry->ce_flags |= ZEND_ACC_FINAL;
+#endif
+
+    return class_entry;
+}
+
+/* Brotli UnCompress Context */
+zend_class_entry *php_brotli_uncompress_context_ce;
+static zend_object_handlers php_brotli_uncompress_context_object_handlers;
+
+static zend_object *
+php_brotli_uncompress_context_create_object(zend_class_entry *class_type)
+{
+    return php_brotli_context_create_object(
+        class_type,
+        &php_brotli_uncompress_context_object_handlers);
+}
+
+static zend_function *
+php_brotli_uncompress_context_get_constructor(zend_object *object)
+{
+  zend_throw_error(NULL,
+                   "Cannot directly construct Brotli\\UnCompress\\Context, "
+                   "use brotli_uncompress_init() instead");
+  return NULL;
+}
+
+static zend_class_entry *php_brotli_uncompress_context_register_class(void)
+{
+    zend_class_entry ce, *class_entry;
+
+    INIT_NS_CLASS_ENTRY(ce, "Brotli\\UnCompress", "Context", NULL);
+#if PHP_VERSION_ID >= 80000
+    class_entry = zend_register_internal_class_ex(&ce, NULL);
+#if PHP_VERSION_ID >= 80100
+    class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE;
+#else
+    class_entry->ce_flags |= ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES;
+#endif
+#else
+    ce.create_object = php_brotli_uncompress_context_create_object;
+    class_entry = zend_register_internal_class(&ce);
+    class_entry->ce_flags |= ZEND_ACC_FINAL;
+#endif
+
+    return class_entry;
+}
+
 ZEND_MINIT_FUNCTION(brotli)
 {
     ZEND_INIT_MODULE_GLOBALS(brotli, php_brotli_init_globals, NULL);
@@ -885,15 +984,67 @@ ZEND_MINIT_FUNCTION(brotli)
     REGISTER_LONG_CONSTANT("BROTLI_FINISH", BROTLI_OPERATION_FINISH,
                            CONST_CS | CONST_PERSISTENT);
 
-    le_state = zend_register_list_destructors_ex(php_brotli_state_rsrc_dtor,
-                                                 NULL, "brotli.state",
-                                                 module_number);
-
     php_output_handler_alias_register(ZEND_STRL(PHP_BROTLI_OUTPUT_HANDLER),
                                       php_brotli_output_handler_init);
     php_output_handler_conflict_register(ZEND_STRL(PHP_BROTLI_OUTPUT_HANDLER),
                                          php_brotli_output_conflict);
 
+    php_brotli_compress_context_ce
+        = php_brotli_compress_context_register_class();
+#if PHP_VERSION_ID >= 80000
+    php_brotli_compress_context_ce->create_object
+        = php_brotli_compress_context_create_object;
+#if PHP_VERSION_ID >= 80300
+    php_brotli_compress_context_ce->default_object_handlers
+        = &php_brotli_compress_context_object_handlers;
+#endif
+#if PHP_VERSION_ID < 80100
+    php_brotli_compress_context_ce->serialize = zend_class_serialize_deny;
+    php_brotli_compress_context_ce->unserialize = zend_class_unserialize_deny;
+#endif
+#endif
+    memcpy(&php_brotli_compress_context_object_handlers,
+           &std_object_handlers, sizeof(zend_object_handlers));
+    php_brotli_compress_context_object_handlers.offset
+        = XtOffsetOf(php_brotli_context, std);
+    php_brotli_compress_context_object_handlers.free_obj
+        = php_brotli_context_free_obj;
+    php_brotli_compress_context_object_handlers.get_constructor
+        = php_brotli_compress_context_get_constructor;
+    php_brotli_compress_context_object_handlers.clone_obj = NULL;
+#if PHP_VERSION_ID >= 80000
+    php_brotli_compress_context_object_handlers.compare
+        = zend_objects_not_comparable;
+#endif
+
+    php_brotli_uncompress_context_ce
+        = php_brotli_uncompress_context_register_class();
+#if PHP_VERSION_ID >= 80000
+    php_brotli_uncompress_context_ce->create_object
+        = php_brotli_uncompress_context_create_object;
+#if PHP_VERSION_ID >= 80300
+    php_brotli_uncompress_context_ce->default_object_handlers
+        = &php_brotli_uncompress_context_object_handlers;
+#endif
+#if PHP_VERSION_ID < 80100
+    php_brotli_uncompress_context_ce->serialize = zend_class_serialize_deny;
+    php_brotli_uncompress_context_ce->unserialize = zend_class_unserialize_deny;
+#endif
+#endif
+    memcpy(&php_brotli_uncompress_context_object_handlers,
+           &std_object_handlers, sizeof(zend_object_handlers));
+    php_brotli_uncompress_context_object_handlers.offset
+        = XtOffsetOf(php_brotli_context, std);
+    php_brotli_uncompress_context_object_handlers.free_obj
+        = php_brotli_context_free_obj;
+    php_brotli_uncompress_context_object_handlers.get_constructor
+        = php_brotli_uncompress_context_get_constructor;
+    php_brotli_uncompress_context_object_handlers.clone_obj = NULL;
+#if PHP_VERSION_ID >= 80000
+    php_brotli_uncompress_context_object_handlers.compare
+        = zend_objects_not_comparable;
+#endif
+
     REGISTER_INI_ENTRIES();
 
     php_register_url_stream_wrapper(STREAM_NAME,
@@ -1032,7 +1183,6 @@ static ZEND_FUNCTION(brotli_compress_init)
 {
     zend_long quality = BROTLI_DEFAULT_QUALITY;
     zend_long mode =  BROTLI_MODE_GENERIC;
-    php_brotli_state_context *ctx;
 
     ZEND_PARSE_PARAMETERS_START(0, 2)
         Z_PARAM_OPTIONAL
@@ -1040,37 +1190,49 @@ static ZEND_FUNCTION(brotli_compress_init)
         Z_PARAM_LONG(mode)
     ZEND_PARSE_PARAMETERS_END();
 
-    ctx = php_brotli_state_init();
+    PHP_BROTLI_CONTEXT_OBJ_INIT_OF_CLASS(php_brotli_compress_context_ce);
 
-    if (php_brotli_encoder_create(&ctx->encoder,
+    if (php_brotli_encoder_create(&ctx->state.encoder,
                                   quality, 0, mode) != SUCCESS) {
+        zval_ptr_dtor(return_value);
         php_error_docref(NULL, E_WARNING,
                          "Brotli incremental compress init failed");
         RETURN_FALSE;
     }
-
-    RETURN_RES(zend_register_resource(ctx, le_state));
 }
 
 static ZEND_FUNCTION(brotli_compress_add)
 {
     zval *res;
-    php_brotli_state_context *ctx;
+    php_brotli_context *ctx;
     size_t buffer_size, buffer_used;
     zend_long mode = BROTLI_OPERATION_FLUSH;
+#if PHP_VERSION_ID >= 80000
+    zend_object *obj;
+#else
+    zval *obj;
+#endif
     char *in_buf;
     size_t in_size;
     smart_string out = {0};
 
     ZEND_PARSE_PARAMETERS_START(2, 3)
-        Z_PARAM_RESOURCE(res)
+#if PHP_VERSION_ID >= 80000
+        Z_PARAM_OBJ_OF_CLASS(obj, php_brotli_compress_context_ce)
+#else
+        Z_PARAM_OBJECT_OF_CLASS(obj, php_brotli_compress_context_ce)
+#endif
         Z_PARAM_STRING(in_buf, in_size)
         Z_PARAM_OPTIONAL
         Z_PARAM_LONG(mode)
     ZEND_PARSE_PARAMETERS_END();
 
-    ctx = zend_fetch_resource(Z_RES_P(res), NULL, le_state);
-    if (ctx == NULL || ctx->encoder == NULL) {
+#if PHP_VERSION_ID >= 80000
+    ctx = php_brotli_context_from_obj(obj);
+#else
+    ctx = php_brotli_context_from_obj(Z_OBJ_P(obj));
+#endif
+    if (ctx == NULL || ctx->state.encoder == NULL) {
         php_error_docref(NULL, E_WARNING,
                          "Brotli incremental compress resource failed");
         RETURN_FALSE;
@@ -1080,20 +1242,21 @@ static ZEND_FUNCTION(brotli_compress_add)
     buffer_size = (buffer_size < 64) ? 64 : buffer_size;
     uint8_t *buffer = (uint8_t *)emalloc(buffer_size);
 
-    const uint8_t *next_in = in_buf;
-    size_t available_in = in_size;
+    ctx->next_in = in_buf;
+    ctx->available_in = in_size;
 
-    while (available_in || BrotliEncoderHasMoreOutput(ctx->encoder)) {
-        size_t available_out = buffer_size;
-        uint8_t *next_out = buffer;
-        if (BrotliEncoderCompressStream(ctx->encoder,
+    while (ctx->available_in
+           || BrotliEncoderHasMoreOutput(ctx->state.encoder)) {
+        ctx->available_out = buffer_size;
+        ctx->next_out = buffer;
+        if (BrotliEncoderCompressStream(ctx->state.encoder,
                                         mode,
-                                        &available_in,
-                                        &next_in,
-                                        &available_out,
-                                        &next_out,
+                                        &ctx->available_in,
+                                        &ctx->next_in,
+                                        &ctx->available_out,
+                                        &ctx->next_out,
                                         0)) {
-            buffer_used = (size_t)(next_out - buffer);
+            buffer_used = (size_t)(ctx->next_out - buffer);
             if (buffer_used) {
                 smart_string_appendl(&out, buffer, buffer_used);
             }
@@ -1107,17 +1270,17 @@ static ZEND_FUNCTION(brotli_compress_add)
     }
 
     if (mode == BROTLI_OPERATION_FINISH) {
-        while (!BrotliEncoderIsFinished(ctx->encoder)) {
-            size_t available_out = buffer_size;
-            uint8_t *next_out = buffer;
-            if (BrotliEncoderCompressStream(ctx->encoder,
+        while (!BrotliEncoderIsFinished(ctx->state.encoder)) {
+            ctx->available_out = buffer_size;
+            ctx->next_out = buffer;
+            if (BrotliEncoderCompressStream(ctx->state.encoder,
                                             BROTLI_OPERATION_FINISH,
-                                            &available_in,
-                                            &next_in,
-                                            &available_out,
-                                            &next_out,
+                                            &ctx->available_in,
+                                            &ctx->next_in,
+                                            &ctx->available_out,
+                                            &ctx->next_out,
                                             0)) {
-                buffer_used = (size_t)(next_out - buffer);
+                buffer_used = (size_t)(ctx->next_out - buffer);
                 if (buffer_used) {
                     smart_string_appendl(&out, buffer, buffer_used);
                 }
@@ -1195,42 +1358,51 @@ static ZEND_FUNCTION(brotli_uncompress)
 
 static ZEND_FUNCTION(brotli_uncompress_init)
 {
-    php_brotli_state_context *ctx;
-
     if (zend_parse_parameters_none() == FAILURE) {
         RETURN_FALSE;
     }
 
-    ctx = php_brotli_state_init();
+    PHP_BROTLI_CONTEXT_OBJ_INIT_OF_CLASS(php_brotli_uncompress_context_ce);
 
-    if (php_brotli_decoder_create(&ctx->decoder) != SUCCESS) {
+    if (php_brotli_decoder_create(&ctx->state.decoder) != SUCCESS) {
         php_error_docref(NULL, E_WARNING,
                          "Brotli incremental uncompress init failed");
         RETURN_FALSE;
     }
-
-    RETURN_RES(zend_register_resource(ctx, le_state));
 }
 
 static ZEND_FUNCTION(brotli_uncompress_add)
 {
     zval *res;
-    php_brotli_state_context *ctx;
+    php_brotli_context *ctx;
     size_t buffer_size;
     zend_long mode = BROTLI_OPERATION_FLUSH;
+#if PHP_VERSION_ID >= 80000
+    zend_object *obj;
+#else
+    zval *obj;
+#endif
     char *in_buf;
     size_t in_size;
     smart_string out = {0};
 
     ZEND_PARSE_PARAMETERS_START(2, 3)
-        Z_PARAM_RESOURCE(res)
+#if PHP_VERSION_ID >= 80000
+        Z_PARAM_OBJ_OF_CLASS(obj, php_brotli_uncompress_context_ce)
+#else
+        Z_PARAM_OBJECT_OF_CLASS(obj, php_brotli_uncompress_context_ce)
+#endif
         Z_PARAM_STRING(in_buf, in_size)
         Z_PARAM_OPTIONAL
         Z_PARAM_LONG(mode)
     ZEND_PARSE_PARAMETERS_END();
 
-    ctx = zend_fetch_resource(Z_RES_P(res), NULL, le_state);
-    if (ctx == NULL || ctx->decoder == NULL) {
+#if PHP_VERSION_ID >= 80000
+    ctx = php_brotli_context_from_obj(obj);
+#else
+    ctx = php_brotli_context_from_obj(Z_OBJ_P(obj));
+#endif
+    if (ctx == NULL || ctx->state.decoder == NULL) {
         php_error_docref(NULL, E_WARNING,
                          "Brotli incremental uncompress resource failed");
         RETURN_FALSE;
@@ -1243,18 +1415,20 @@ static ZEND_FUNCTION(brotli_uncompress_add)
     buffer_size = PHP_BROTLI_BUFFER_SIZE;
     uint8_t *buffer = (uint8_t *)emalloc(buffer_size);
 
-    const uint8_t *next_in = (const uint8_t *)in_buf;
-    size_t available_in = in_size;
+    ctx->next_in = (const uint8_t *)in_buf;
+    ctx->available_in = in_size;
 
     BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
     while (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
-        size_t available_out = buffer_size;
-        uint8_t *next_out = buffer;
-        result = BrotliDecoderDecompressStream(ctx->decoder,
-                                               &available_in, &next_in,
-                                               &available_out, &next_out,
+        ctx->available_out = buffer_size;
+        ctx->next_out = buffer;
+        result = BrotliDecoderDecompressStream(ctx->state.decoder,
+                                               &ctx->available_in,
+                                               &ctx->next_in,
+                                               &ctx->available_out,
+                                               &ctx->next_out,
                                                0);
-        size_t buffer_used = buffer_size - available_out;
+        size_t buffer_used = buffer_size - ctx->available_out;
         if (buffer_used) {
             smart_string_appendl(&out, buffer, buffer_used);
         }
diff --git a/brotli.stub.php b/brotli.stub.php
index a038c67..7c55c00 100644
--- a/brotli.stub.php
+++ b/brotli.stub.php
@@ -2,56 +2,104 @@
 
 namespace {
 
-  function brotli_compress(string $data, int $quality = BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = BROTLI_GENERIC): string|false {}
-
-  function brotli_uncompress(string $data, int $length = 0): string|false {}
-
   /**
-   * @return resource|false
+   * @var int
+   * @cvalue BROTLI_MODE_GENERIC
    */
-  function brotli_compress_init(int $quality = BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = BROTLI_GENERIC) {}
+  const BROTLI_GENERIC = UNKNOWN;
 
   /**
-   * @param resource $context
+   * @var int
+   * @cvalue BROTLI_MODE_TEXT
    */
-  function brotli_compress_add($context, string $data, int $mode = BROTLI_FLUSH): string|false {}
+  const BROTLI_TEXT = UNKNOWN;
 
   /**
-   * @return resource|false
+   * @var int
+   * @cvalue BROTLI_MODE_FONT
    */
-  function brotli_uncompress_init() {}
+  const BROTLI_FONT = UNKNOWN;
 
   /**
-   * @param resource $context
+   * @var int
+   * @cvalue BROTLI_MIN_QUALITY
    */
-  function brotli_uncompress_add($context, string $data, int $mode = BROTLI_FLUSH): string|false {}
-}
+  const BROTLI_COMPRESS_LEVEL_MIN = UNKNOWN;
 
-namespace Brotli {
-
-  function compress(string $data, int $quality = \BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = \BROTLI_GENERIC): string|false {}
-
-  function ncompress(string $data, int $length = 0): string|false {}
+  /**
+   * @var int
+   * @cvalue BROTLI_MAX_QUALITY
+   */
+  const BROTLI_COMPRESS_LEVEL_MAX = UNKNOWN;
 
   /**
-   * @return resource|false
+   * @var int
+   * @cvalue BROTLI_DEFAULT_QUALITY
    */
-  function compress_init(int $quality = \BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = \BROTLI_GENERIC) {}
+  const BROTLI_COMPRESS_LEVEL_DEFAULT = UNKNOWN;
 
   /**
-   * @param resource $context
+   * @var int
+   * @cvalue BROTLI_OPERATION_PROCESS
    */
-  function compress_add($context, string $data, int $mode = \BROTLI_FLUSH): string|false {}
+  const BROTLI_PROCESS = UNKNOWN;
 
   /**
-   * @return resource|false
+   * @var int
+   * @cvalue BROTLI_OPERATION_FLUSH
    */
-  function uncompress_init() {}
+  const BROTLI_FLUSH = UNKNOWN;
 
   /**
-   * @param resource $context
+   * @var int
+   * @cvalue BROTLI_OPERATION_FINISH
    */
-  function uncompress_add($context, string $data, int $mode = \BROTLI_FLUSH): string|false {}
+  const BROTLI_FINISH = UNKNOWN;
+
+
+  function brotli_compress(string $data, int $quality = BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = BROTLI_GENERIC): string|false {}
+
+  function brotli_uncompress(string $data, int $length = 0): string|false {}
+
+  function brotli_compress_init(int $quality = BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = BROTLI_GENERIC): Brotli\Compress\Context|false {}
+
+  function brotli_compress_add(Brotli\Compress\Context $context, string $data, int $mode = BROTLI_FLUSH): string|false {}
+
+  function brotli_uncompress_init(): Brotli\UnCompress\Context|false {}
+
+  function brotli_uncompress_add(Brotli\UnCompress\Context $context, string $data, int $mode = BROTLI_FLUSH): string|false {}
+}
+
+namespace Brotli {
+
+  function compress(string $data, int $quality = \BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = \BROTLI_GENERIC): string|false {}
+
+  function uncompress(string $data, int $length = 0): string|false {}
+
+  function compress_init(int $quality = \BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = \BROTLI_GENERIC): Compress\Context|false {}
+
+  function compress_add(Compress\Context $context, string $data, int $mode = \BROTLI_FLUSH): string|false {}
+
+  function uncompress_init(): UnCompress\Context|false {}
+
+  function uncompress_add(UnCompress\Context $context, string $data, int $mode = \BROTLI_FLUSH): string|false {}
+
+}
+
+namespace Brotli\Compress {
+
+  final class Context
+  {
+  }
+
+}
+
+namespace Brotli\UnCompress {
+
+  final class Context
+  {
+  }
+
 }
 
 #if defined(PHP_WIN32) || defined(HAVE_DNS_SEARCH_FUNC)
diff --git a/php_brotli.h b/php_brotli.h
index 5eb1d39..42c996e 100644
--- a/php_brotli.h
+++ b/php_brotli.h
@@ -40,6 +40,7 @@ typedef struct _php_brotli_context {
     size_t available_out;
     uint8_t *next_out;
     uint8_t *output;
+    zend_object std;
 } php_brotli_context;
 
 ZEND_BEGIN_MODULE_GLOBALS(brotli)


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>README.md (1)</summary><blockquote>

`290-297`: **Rename `$resource` in the examples for clarity.**  
The incremental‑API now returns real objects, yet the examples keep the legacy variable name `$resource` which can confuse users skimming the docs.

```diff
-$resource = brotli_compress_init();
+$ctx = brotli_compress_init();
 ...
-$compressed .= brotli_compress_add($resource, 'Hello, ', BROTLI_FLUSH);
-$compressed .= brotli_compress_add($resource, 'World!', BROTLI_FLUSH);
-$compressed .= brotli_compress_add($resource, '', BROTLI_FINISH);
+$compressed .= brotli_compress_add($ctx, 'Hello, ', BROTLI_FLUSH);
+$compressed .= brotli_compress_add($ctx, 'World!', BROTLI_FLUSH);
+$compressed .= brotli_compress_add($ctx, '', BROTLI_FINISH);
```

Same for the decompression example a few lines below.

</blockquote></details>
<details>
<summary>brotli.stub.php (1)</summary><blockquote>

`60-70`: **Optional: provide concrete constant values in stubs for IDE support.**  
`UNKNOWN` markers work, but most IDEs (PHPStorm, Psalm, etc.) can offer better autocompletion / static analysis when the real literal is present.  Consider exporting the numeric values via `generate_stubs.php` or a small C helper.

</blockquote></details>
<details>
<summary>brotli.c (2)</summary><blockquote>

`1284-1289`: **Pointer‑sign warning – cast to `const uint8_t*`.**

`ctx->next_in` is declared as `const uint8_t*`, but `in_buf` is `char*`.
On several compilers this produces a strict‑aliasing / pointer‑sign warning.

```diff
-    ctx->next_in = in_buf;
+    ctx->next_in = (const uint8_t *)in_buf;
```

---

`1417-1420`: **Secondary unused variable (`res`) in `brotli_uncompress_add`.**  
Same as above – safe to drop.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6eb42b86a29e09b9cb0f4482ef52758e06b54d23 and 98d30f0271b4855d1ca2ebeee3fac33a338a3e03.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `README.md` (4 hunks)
* `brotli.c` (8 hunks)
* `brotli.stub.php` (1 hunks)
* `php_brotli.h` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>php_brotli.h (1)</summary>

`43-44`: **`zend_object std;` looks good – but double‑check ABI compatibility.**  
Placing `zend_object` as the *last* field is the expected layout for “struct -with‑object” patterns and is consistent with the `XtOffsetOf()` usage in `brotli.c`.  Just make sure no C code still copies the struct by value (it would silently drop the trailing `std` bytes).

</details>
<details>
<summary>brotli.c (1)</summary>

`1246-1249`: **Remove unused variable `res`.**  
The variable is never referenced and triggers -Wunused‑parameter warnings.

```diff
-    zval *res;
```
[ suggest_nitpick ]

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-22T00:50:12Z
user: login: kjdev
state: COMMENTED
author_association: OWNER
submitted_at: 2025-04-22T01:42:45Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-22T01:44:46Z

Review comments:
diff_hunk: @@ -859,6 +829,174 @@ php_stream_wrapper php_stream_brotli_wrapper = {
     0 /* is_url */
 };
 
+/* Brotli Compress/UnCompress Context */
+#define PHP_BROTLI_CONTEXT_OBJ_INIT_OF_CLASS(ce) \
+  php_brotli_context *ctx; \
+  object_init_ex(return_value, ce); \
+  ctx = php_brotli_context_from_obj(Z_OBJ_P(return_value)); \
+  ctx->state.encoder = NULL; \
+  ctx->state.decoder = NULL; \
+  ctx->available_in = 0; \
+  ctx->next_in = NULL; \
+  ctx->available_out = 0; \
+  ctx->next_out = NULL; \
+  ctx->output = NULL;
+
+static php_brotli_context *php_brotli_context_from_obj(zend_object *obj)
+{
+    return (php_brotli_context *)
+        ((char *)(obj) - XtOffsetOf(php_brotli_context, std));
+}
+
+static void php_brotli_context_free_obj(zend_object *object)
+{
+    php_brotli_context *intern = php_brotli_context_from_obj(object);
+    php_brotli_context_close(intern);
+    zend_object_std_dtor(&intern->std);
+}
+
+#if PHP_VERSION_ID >= 80300
+static zend_object *
+php_brotli_context_create_object(zend_class_entry *class_type)
+{
+    php_brotli_context *intern = zend_object_alloc(sizeof(php_brotli_context),
+                                                   class_type);
+    zend_object_std_init(&intern->std, class_type);
+    object_properties_init(&intern->std, class_type);
+    return &intern->std;
+}
path: brotli.c
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Missing `std.handlers` assignment → objects leak / wrong destructor.**  

For PHP ≥ 8.3 the custom creator never assigns the object handlers.  
As a result the object keeps the default handlers, and `free_obj` (which
releases the Brotli state) is **never called**, causing encoder/decoder
instances to leak.

```diff
 zend_object_std_init(&intern->std, class_type);
 object_properties_init(&intern->std, class_type);
+/* hook up our custom handlers so dtor / comparisons work */
+intern->std.handlers = class_type->default_object_handlers
+                       ? class_type->default_object_handlers
+                       : &std_object_handlers;
 return &intern->std;
```

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-22T00:50:11Z
author_association: NONE
diff_hunk: @@ -859,6 +829,174 @@ php_stream_wrapper php_stream_brotli_wrapper = {
     0 /* is_url */
 };
 
+/* Brotli Compress/UnCompress Context */
+#define PHP_BROTLI_CONTEXT_OBJ_INIT_OF_CLASS(ce) \
+  php_brotli_context *ctx; \
+  object_init_ex(return_value, ce); \
+  ctx = php_brotli_context_from_obj(Z_OBJ_P(return_value)); \
+  ctx->state.encoder = NULL; \
+  ctx->state.decoder = NULL; \
+  ctx->available_in = 0; \
+  ctx->next_in = NULL; \
+  ctx->available_out = 0; \
+  ctx->next_out = NULL; \
+  ctx->output = NULL;
+
+static php_brotli_context *php_brotli_context_from_obj(zend_object *obj)
+{
+    return (php_brotli_context *)
+        ((char *)(obj) - XtOffsetOf(php_brotli_context, std));
+}
+
+static void php_brotli_context_free_obj(zend_object *object)
+{
+    php_brotli_context *intern = php_brotli_context_from_obj(object);
+    php_brotli_context_close(intern);
+    zend_object_std_dtor(&intern->std);
+}
+
+#if PHP_VERSION_ID >= 80300
+static zend_object *
+php_brotli_context_create_object(zend_class_entry *class_type)
+{
+    php_brotli_context *intern = zend_object_alloc(sizeof(php_brotli_context),
+                                                   class_type);
+    zend_object_std_init(&intern->std, class_type);
+    object_properties_init(&intern->std, class_type);
+    return &intern->std;
+}
path: brotli.c
user: login: kjdev
body: @coderabbitai review 
created_at: 2025-04-22T01:42:45Z
author_association: OWNER
diff_hunk: @@ -859,6 +829,174 @@ php_stream_wrapper php_stream_brotli_wrapper = {
     0 /* is_url */
 };
 
+/* Brotli Compress/UnCompress Context */
+#define PHP_BROTLI_CONTEXT_OBJ_INIT_OF_CLASS(ce) \
+  php_brotli_context *ctx; \
+  object_init_ex(return_value, ce); \
+  ctx = php_brotli_context_from_obj(Z_OBJ_P(return_value)); \
+  ctx->state.encoder = NULL; \
+  ctx->state.decoder = NULL; \
+  ctx->available_in = 0; \
+  ctx->next_in = NULL; \
+  ctx->available_out = 0; \
+  ctx->next_out = NULL; \
+  ctx->output = NULL;
+
+static php_brotli_context *php_brotli_context_from_obj(zend_object *obj)
+{
+    return (php_brotli_context *)
+        ((char *)(obj) - XtOffsetOf(php_brotli_context, std));
+}
+
+static void php_brotli_context_free_obj(zend_object *object)
+{
+    php_brotli_context *intern = php_brotli_context_from_obj(object);
+    php_brotli_context_close(intern);
+    zend_object_std_dtor(&intern->std);
+}
+
+#if PHP_VERSION_ID >= 80300
+static zend_object *
+php_brotli_context_create_object(zend_class_entry *class_type)
+{
+    php_brotli_context *intern = zend_object_alloc(sizeof(php_brotli_context),
+                                                   class_type);
+    zend_object_std_init(&intern->std, class_type);
+    object_properties_init(&intern->std, class_type);
+    return &intern->std;
+}
path: brotli.c
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

@kjdev I've reviewed the code regarding the potential issue with object handlers in PHP ≥ 8.3 that was previously identified.

I'm happy to report that the current implementation properly handles object creation and destruction for all PHP versions. The specific issue mentioned in the previous review (missing `std.handlers` assignment in the PHP 8.3-specific implementation) doesn't appear to be present in the code.

The current implementation:
1. Has a generic `php_brotli_context_create_object` function that correctly takes handlers as a parameter and assigns them
2. Both specialized context creation functions (`php_brotli_compress_context_create_object` and `php_brotli_uncompress_context_create_object`) correctly pass their handlers to this function
3. The handlers are properly initialized with the correct `free_obj` function that will clean up the Brotli state

No changes are needed as the code already handles this correctly.

---

🏁 Script executed:
```
#!/bin/bash
# Check if the issue is still present in the PR branch
grep -A 20 "php_brotli_context_create_object" brotli.c
```

Length of output: 2139

---

🏁 Script executed:
```
#!/bin/bash
# Check the compress and uncompress object creation functions
grep -n -A 20 "php_brotli_compress_context_create_object" brotli.c
grep -n -A 20 "php_brotli_uncompress_context_create_object" brotli.c
```

Length of output: 5395

---

🏁 Script executed:
```
#!/bin/bash
# Get the full context of the php_brotli_context_create_object function
grep -n -A 40 "php_brotli_context_create_object" brotli.c
```

Length of output: 3991

---

🏁 Script executed:
```
#!/bin/bash
# Check the initialization of handlers
grep -n -A 10 "php_brotli_compress_context_object_handlers" brotli.c
grep -n -A 10 "php_brotli_uncompress_context_object_handlers" brotli.c
```

Length of output: 4041

---

🏁 Script executed:
```
#!/bin/bash
# Find where the handlers are initialized
grep -n -A 30 "memcpy.*php_brotli_compress_context_object_handlers" brotli.c
```

Length of output: 1632

---

🏁 Script executed:
```
#!/bin/bash
# Find where the uncompress handlers are initialized
grep -n -A 30 "memcpy.*php_brotli_uncompress_context_object_handlers" brotli.c
```

Length of output: 1313

---

🏁 Script executed:
```
#!/bin/bash
# Check for PHP 8.3-specific implementations
grep -n -B 5 -A 15 "PHP_VERSION_ID >= 80300" brotli.c
```

Length of output: 2186

---

🏁 Script executed:
```
#!/bin/bash
# Check the MINIT function for PHP 8.3-specific code
grep -n -A 100 "ZEND_MINIT_FUNCTION" brotli.c | grep -B 20 -A 20 "80300"
```

Length of output: 3605

---

🏁 Script executed:
```
#!/bin/bash
# Do a more thorough check for PHP 8.3 specific code
grep -n "PHP_VERSION_ID >= 80" brotli.c
```

Length of output: 627

---



</details>

@kjdev I've reviewed the code regarding the object handlers in PHP ≥ 8.3 that was previously identified as an issue.

I'm happy to report that the current implementation correctly handles object creation and destruction for all PHP versions. The specific issue mentioned in the previous review isn't present in the code.

The current implementation is well-structured:
- The `php_brotli_context_create_object` function correctly accepts handlers as a parameter and assigns them to `intern->std.handlers`
- Both specialized creation functions properly pass their respective handlers
- The handlers are correctly initialized with `free_obj = php_brotli_context_free_obj`
- For PHP 8.3+, there's also optimization code that sets `default_object_handlers` on the class

Everything looks good! No changes are needed as the code already handles object lifecycle correctly.

---

<details>
<summary>🧬 Code Graph Analysis Results</summary>

### Relevant snippets from `brotli.stub.php` (lines 75-85)

```php
function compress(string $data, int $quality = \BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = \BROTLI_GENERIC): string|false {}
function uncompress(string $data, int $length = 0): string|false {}
function compress_init(int $quality = \BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = \BROTLI_GENERIC): Compress\Context|false {}
function compress_add(Compress\Context $context, string $data, int $mode = \BROTLI_FLUSH): string|false {}
function uncompress_init(): UnCompress\Context|false {}
function uncompress_add(UnCompress\Context $context, string $data, int $mode = \BROTLI_FLUSH): string|false {}
```

These function declarations correspond directly to the ZEND_FUNCTION implementations in `brotli.c` for the Brotli compression and decompression API, including incremental compression/uncompression contexts and operations.

---

### Summary of key relevant parts in `brotli.c`

- **Functions implementing Brotli compression and decompression:**

  - `brotli_compress` (lines ~400-440): Compresses a string with optional quality and mode parameters, returns compressed string or false on failure.

  - `brotli_uncompress` (lines ~520-560): Decompresses a Brotli compressed string with optional max output size, returns decompressed string or false on failure.

  - `brotli_compress_init` (lines ~445-460): Initializes an incremental compression context object, returns the context or false on failure.

  - `brotli_compress_add` (lines ~465-520): Adds data to an incremental compression context, with optional mode (flush/finish), returns compressed chunk or false on failure.

  - `brotli_uncompress_init` (lines ~565-575): Initializes an incremental decompression context object, returns the context or false on failure.

  - `brotli_uncompress_add` (lines ~580-630): Adds data to an incremental decompression context, with optional mode, returns decompressed chunk or false on failure.

- **Context object management:**

  - `php_brotli_context` struct and related functions for creating, freeing, and managing Brotli compression and decompression contexts.

  - Classes `Brotli\Compress\Context` and `Brotli\UnCompress\Context` are registered internally and cannot be directly instantiated by user code; instead, the init functions must be used.

- **Stream wrapper for `compress.brotli://`** (lines ~350-390): Implements PHP stream wrapper for Brotli compression and decompression, supporting reading and writing streams with Brotli encoding.

- **Output buffering handler** (lines ~130-210): Implements an output handler for Brotli compression of PHP output buffers, including initialization, compression streaming, and cleanup.

- **Module initialization and configuration** (lines ~270-330): Registers constants, classes, INI entries, output handler aliases, and stream wrappers.

- **APCu serializer support** (lines ~640-700): Optional support for serializing and unserializing data compressed with Brotli for APCu caching.

---

This information provides a comprehensive overview of the Brotli PHP extension's core functionality as implemented in `brotli.c`, matching the function signatures declared in the stub file.

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer><details>
<summary>🧩 Analysis chain</summary>

@kjdev I've reviewed the code regarding the potential issue with object handlers in PHP ≥ 8.3 that was previously identified.

I'm happy to report that the current implementation properly handles object creation and destruction for all PHP versions. The specific issue mentioned in the previous review (missing `std.handlers` assignment in the PHP 8.3-specific implementation) doesn't appear to be present in the code.

The current implementation:
1. Has a generic `php_brotli_context_create_object` function that correctly takes handlers as a parameter and assigns them
2. Both specialized context creation functions (`php_brotli_compress_context_create_object` and `php_brotli_uncompress_context_create_object`) correctly pass their handlers to this function
3. The handlers are properly initialized with the correct `free_obj` function that will clean up the Brotli state

No changes are needed as the code already handles this correctly.

---

🏁 Script executed:
```
#!/bin/bash
# Check if the issue is still present in the PR branch
grep -A 20 "php_brotli_context_create_object" brotli.c
```

Length of output: 2139

---

🏁 Script executed:
```
#!/bin/bash
# Check the compress and uncompress object creation functions
grep -n -A 20 "php_brotli_compress_context_create_object" brotli.c
grep -n -A 20 "php_brotli_uncompress_context_create_object" brotli.c
```

Length of output: 5395

---

🏁 Script executed:
```
#!/bin/bash
# Get the full context of the php_brotli_context_create_object function
grep -n -A 40 "php_brotli_context_create_object" brotli.c
```

Length of output: 3991

---

🏁 Script executed:
```
#!/bin/bash
# Check the initialization of handlers
grep -n -A 10 "php_brotli_compress_context_object_handlers" brotli.c
grep -n -A 10 "php_brotli_uncompress_context_object_handlers" brotli.c
```

Length of output: 4041

---

🏁 Script executed:
```
#!/bin/bash
# Find where the handlers are initialized
grep -n -A 30 "memcpy.*php_brotli_compress_context_object_handlers" brotli.c
```

Length of output: 1632

---

🏁 Script executed:
```
#!/bin/bash
# Find where the uncompress handlers are initialized
grep -n -A 30 "memcpy.*php_brotli_uncompress_context_object_handlers" brotli.c
```

Length of output: 1313

---

🏁 Script executed:
```
#!/bin/bash
# Check for PHP 8.3-specific implementations
grep -n -B 5 -A 15 "PHP_VERSION_ID >= 80300" brotli.c
```

Length of output: 2186

---

🏁 Script executed:
```
#!/bin/bash
# Check the MINIT function for PHP 8.3-specific code
grep -n -A 100 "ZEND_MINIT_FUNCTION" brotli.c | grep -B 20 -A 20 "80300"
```

Length of output: 3605

---

🏁 Script executed:
```
#!/bin/bash
# Do a more thorough check for PHP 8.3 specific code
grep -n "PHP_VERSION_ID >= 80" brotli.c
```

Length of output: 627

---



</details>

@kjdev I've reviewed the code regarding the object handlers in PHP ≥ 8.3 that was previously identified as an issue.

I'm happy to report that the current implementation correctly handles object creation and destruction for all PHP versions. The specific issue mentioned in the previous review isn't present in the code.

The current implementation is well-structured:
- The `php_brotli_context_create_object` function correctly accepts handlers as a parameter and assigns them to `intern->std.handlers`
- Both specialized creation functions properly pass their respective handlers
- The handlers are correctly initialized with `free_obj = php_brotli_context_free_obj`
- For PHP 8.3+, there's also optimization code that sets `default_object_handlers` on the class

Everything looks good! No changes are needed as the code already handles object lifecycle correctly.

---

<details>
<summary>🧬 Code Graph Analysis Results</summary>

### Relevant snippets from `brotli.stub.php` (lines 75-85)

```php
function compress(string $data, int $quality = \BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = \BROTLI_GENERIC): string|false {}
function uncompress(string $data, int $length = 0): string|false {}
function compress_init(int $quality = \BROTLI_COMPRESS_LEVEL_DEFAULT, int $mode = \BROTLI_GENERIC): Compress\Context|false {}
function compress_add(Compress\Context $context, string $data, int $mode = \BROTLI_FLUSH): string|false {}
function uncompress_init(): UnCompress\Context|false {}
function uncompress_add(UnCompress\Context $context, string $data, int $mode = \BROTLI_FLUSH): string|false {}
```

These function declarations correspond directly to the ZEND_FUNCTION implementations in `brotli.c` for the Brotli compression and decompression API, including incremental compression/uncompression contexts and operations.

---

### Summary of key relevant parts in `brotli.c`

- **Functions implementing Brotli compression and decompression:**

  - `brotli_compress` (lines ~400-440): Compresses a string with optional quality and mode parameters, returns compressed string or false on failure.

  - `brotli_uncompress` (lines ~520-560): Decompresses a Brotli compressed string with optional max output size, returns decompressed string or false on failure.

  - `brotli_compress_init` (lines ~445-460): Initializes an incremental compression context object, returns the context or false on failure.

  - `brotli_compress_add` (lines ~465-520): Adds data to an incremental compression context, with optional mode (flush/finish), returns compressed chunk or false on failure.

  - `brotli_uncompress_init` (lines ~565-575): Initializes an incremental decompression context object, returns the context or false on failure.

  - `brotli_uncompress_add` (lines ~580-630): Adds data to an incremental decompression context, with optional mode, returns decompressed chunk or false on failure.

- **Context object management:**

  - `php_brotli_context` struct and related functions for creating, freeing, and managing Brotli compression and decompression contexts.

  - Classes `Brotli\Compress\Context` and `Brotli\UnCompress\Context` are registered internally and cannot be directly instantiated by user code; instead, the init functions must be used.

- **Stream wrapper for `compress.brotli://`** (lines ~350-390): Implements PHP stream wrapper for Brotli compression and decompression, supporting reading and writing streams with Brotli encoding.

- **Output buffering handler** (lines ~130-210): Implements an output handler for Brotli compression of PHP output buffers, including initialization, compression streaming, and cleanup.

- **Module initialization and configuration** (lines ~270-330): Registers constants, classes, INI entries, output handler aliases, and stream wrappers.

- **APCu serializer support** (lines ~640-700): Optional support for serializing and unserializing data compressed with Brotli for APCu caching.

---

This information provides a comprehensive overview of the Brotli PHP extension's core functionality as implemented in `brotli.c`, matching the function signatures declared in the stub file.

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-04-22T01:44:46Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-22T00:47:37Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Draft detected.
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes modernize the Brotli PHP extension by replacing legacy resource-based state management for incremental compression and decompression with dedicated PHP objects. New final classes, `Brotli\Compress\Context` and `Brotli\UnCompress\Context`, are introduced to encapsulate context state, and all related functions are updated to use these objects instead of generic resources. The documentation, stubs, and internal implementation are updated accordingly, including the addition of explicit type hints, constants, and namespace organization to improve clarity and type safety.

## Changes

| File(s)                                   | Change Summary                                                                                                                |
|--------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
| README.md                                 | Updated documentation to reflect the switch from resource types to `Brotli\Compress\Context` and `Brotli\UnCompress\Context` objects in function signatures and descriptions. |
| brotli.c                                  | Replaced resource-based state management with PHP object-based contexts; introduced new classes for compression and decompression contexts; updated all relevant functions and internal logic to use these objects; registered new classes and handlers in module initialization. |
| brotli.stub.php                           | Expanded and restructured stubs: added comprehensive Brotli constants, introduced explicit class types for context parameters and return types, created new namespaces and final context classes, and updated all function signatures accordingly. |
| php_brotli.h                              | Added `zend_object std;` member to the `php_brotli_context` struct to enable object-based context management in PHP.          |

## Poem

> 🐰  
> Out with the resources, in with the class,  
> Brotli's new objects have plenty of sass!  
> Compression and context, now tidy and neat,  
> With namespaces, constants, and type hints complete.  
> The code hops forward, so modern and bright—  
> PHP and Brotli, a future in sight!  
> 🥕

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=kjdev/php-ext-brotli&utm_content=58):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: kjdev
created_at: 2025-04-22T00:47:57Z
body: @coderabbitai review
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-04-22T00:48:01Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>:white_check_mark: Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: NONE