├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── config.m4 ├── config.w32 ├── libdeflate.c ├── php_libdeflate.h └── tests ├── invalid-compression-level.phpt └── normal-compression.phpt /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-20.04 11 | if: "!contains(github.event.head_commit.message, '[ci skip]')" 12 | 13 | strategy: 14 | matrix: 15 | php: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4'] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Setup PHP 21 | uses: shivammathur/setup-php@2.27.1 22 | with: 23 | php-version: "${{ matrix.php }}" 24 | 25 | - name: Build libdeflate 26 | run: | 27 | git clone --depth=1 https://github.com/ebiggers/libdeflate.git -b v1.23 28 | cd libdeflate 29 | cmake . -DCMAKE_PREFIX_PATH=. -DCMAKE_INSTALL_PREFIX=. 30 | make install 31 | cd .. 32 | 33 | - name: Build extension 34 | run: | 35 | phpize 36 | PKG_CONFIG_PATH="${{ github.workspace }}/libdeflate/lib/pkgconfig" ./configure --with-libdeflate 37 | make 38 | 39 | - name: Run .phpt tests 40 | run: REPORT_EXIT_STATUS=1 NO_INTERACTION=1 TEST_PHP_ARGS="--show-diff" make test 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.lo 3 | *.la 4 | .libs 5 | acinclude.m4 6 | aclocal.m4 7 | autom4te.cache 8 | build 9 | config.guess 10 | config.h 11 | config.h.in 12 | config.log 13 | config.nice 14 | config.status 15 | config.sub 16 | configure 17 | configure.ac 18 | configure.in 19 | include 20 | install-sh 21 | libtool 22 | ltmain.sh 23 | Makefile 24 | Makefile.fragments 25 | Makefile.global 26 | Makefile.objects 27 | missing 28 | mkinstalldirs 29 | modules 30 | run-tests.php 31 | tests/*/*.diff 32 | tests/*/*.out 33 | tests/*/*.php 34 | tests/*/*.exp 35 | tests/*/*.log 36 | tests/*/*.sh 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Dylan K. Taylor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ext-libdeflate 2 | [![CI](https://github.com/pmmp/ext-libdeflate/actions/workflows/ci.yml/badge.svg)](https://github.com/pmmp/ext-libdeflate/actions/workflows/ci.yml) 3 | 4 | PHP bindings for [libdeflate](https://github.com/ebiggers/libdeflate), a zlib replacement with significantly better performance. 5 | 6 | ## Features 7 | At the time of writing, this extension only exposes libdeflate's compression APIs, since these are the simplest to implement. 8 | 9 | Since libdeflate doesn't support stream decompression, the decompression APIs are less useful unless you know the decompressed size of the data before decompressing it. 10 | 11 | ## Performance 12 | Compared to `zlib_encode()`, a 40% performance improvement can be observed at level 1, rising to 50-70% at level 6 (default), for approximately the same compression ratios. Also, `libdeflate_deflate_compress()` was observed to sometimes be faster at level 6 compression than `zlib_encode()` at level 1 compression. 13 | 14 | ## API 15 | ```php 16 | MAX_COMPRESSION_LEVEL) { 71 | zend_throw_exception_ex( 72 | spl_ce_InvalidArgumentException, 73 | 0, 74 | "Invalid compression level: %zi (accepted levels: %u...%u)", 75 | level, 76 | 0, 77 | MAX_COMPRESSION_LEVEL 78 | ); 79 | return NULL; 80 | } 81 | 82 | struct libdeflate_compressor* compressor = LIBDEFLATE_G(compressor_cache)[level]; 83 | if (compressor == NULL) { 84 | compressor = libdeflate_alloc_compressor(level); 85 | if (compressor == NULL) { 86 | zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Unable to allocate libdeflate compressor (this is a bug)"); 87 | return NULL; 88 | } 89 | LIBDEFLATE_G(compressor_cache)[level] = compressor; 90 | } 91 | 92 | size_t compressBound = compressBoundFunc(compressor, ZSTR_LEN(data)); 93 | void* output = emalloc(compressBound); 94 | size_t actualSize = compressFunc(compressor, ZSTR_VAL(data), ZSTR_LEN(data), output, compressBound); 95 | 96 | if (actualSize == 0){ 97 | zend_throw_exception_ex(spl_ce_RuntimeException, 0, "Too small buffer provided (this is a bug)"); 98 | return NULL; 99 | } 100 | 101 | zend_string* result = zend_string_init(output, actualSize, 0); 102 | efree(output); 103 | return result; 104 | } 105 | 106 | #define PHP_LIBDEFLATE_FUNC(compressFunc, compressBoundFunc) \ 107 | PHP_FUNCTION(compressFunc) { \ 108 | zend_string *data; \ 109 | zend_long level = 6; \ 110 | \ 111 | ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 1, 2) \ 112 | Z_PARAM_STR(data) \ 113 | Z_PARAM_OPTIONAL \ 114 | Z_PARAM_LONG(level) \ 115 | ZEND_PARSE_PARAMETERS_END(); \ 116 | \ 117 | zend_string *result = php_libdeflate_compress(data, level, compressBoundFunc, compressFunc); \ 118 | if (result == NULL) { \ 119 | return; \ 120 | } \ 121 | RETURN_STR(result); \ 122 | } 123 | 124 | /* {{{ proto string libdeflate_deflate_compress(string $data [, int $level = 6]) */ 125 | PHP_LIBDEFLATE_FUNC(libdeflate_deflate_compress, libdeflate_deflate_compress_bound) /* }}} */ 126 | 127 | /* {{{ proto string libdeflate_zlib_compress(string $data [, int $level = 6]) */ 128 | PHP_LIBDEFLATE_FUNC(libdeflate_zlib_compress, libdeflate_zlib_compress_bound) /* }}} */ 129 | 130 | /* {{{ proto string libdeflate_gzip_compress(string $data [, int $level = 6]) */ 131 | PHP_LIBDEFLATE_FUNC(libdeflate_gzip_compress, libdeflate_gzip_compress_bound) /* }}} */ 132 | 133 | /* {{{ libdeflate_functions[] 134 | */ 135 | static const zend_function_entry libdeflate_functions[] = { 136 | PHP_FE(libdeflate_deflate_compress, arginfo_libdeflate_compress) 137 | PHP_FE(libdeflate_zlib_compress, arginfo_libdeflate_compress) 138 | PHP_FE(libdeflate_gzip_compress, arginfo_libdeflate_compress) 139 | PHP_FE_END 140 | }; 141 | /* }}} */ 142 | 143 | /* {{{ libdeflate_module_entry 144 | */ 145 | zend_module_entry libdeflate_module_entry = { 146 | STANDARD_MODULE_HEADER, 147 | "libdeflate", 148 | libdeflate_functions, 149 | NULL, 150 | NULL, /* PHP_MSHUTDOWN */ 151 | PHP_RINIT(libdeflate), 152 | PHP_RSHUTDOWN(libdeflate), 153 | PHP_MINFO(libdeflate), 154 | PHP_LIBDEFLATE_VERSION, 155 | PHP_MODULE_GLOBALS(libdeflate), 156 | NULL, 157 | NULL, 158 | NULL, 159 | STANDARD_MODULE_PROPERTIES_EX 160 | }; 161 | /* }}} */ 162 | 163 | #ifdef COMPILE_DL_LIBDEFLATE 164 | # ifdef ZTS 165 | ZEND_TSRMLS_CACHE_DEFINE() 166 | # endif 167 | ZEND_GET_MODULE(libdeflate) 168 | #endif 169 | -------------------------------------------------------------------------------- /php_libdeflate.h: -------------------------------------------------------------------------------- 1 | /* libdeflate extension for PHP */ 2 | 3 | #ifndef PHP_LIBDEFLATE_H 4 | # define PHP_LIBDEFLATE_H 5 | 6 | extern zend_module_entry libdeflate_module_entry; 7 | # define phpext_libdeflate_ptr &libdeflate_module_entry 8 | 9 | # define PHP_LIBDEFLATE_VERSION "0.2.2-dev" 10 | 11 | # if defined(ZTS) && defined(COMPILE_DL_LIBDEFLATE) 12 | ZEND_TSRMLS_CACHE_EXTERN() 13 | # endif 14 | 15 | #endif /* PHP_LIBDEFLATE_H */ 16 | -------------------------------------------------------------------------------- /tests/invalid-compression-level.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test that libdeflate_deflate_compress() doesn't segfault when given an invalid compression level 3 | --SKIPIF-- 4 | 5 | --FILE-- 6 | 20 | --EXPECT-- 21 | string(2) "ok" 22 | string(2) "ok" 23 | -------------------------------------------------------------------------------- /tests/normal-compression.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test that compression works normally 3 | --FILE-- 4 | $compressed){ 27 | if($level === 0){ 28 | if(strpos($compressed, $buffer) === false){ 29 | var_dump($compressed); 30 | throw new \Error("Output for level 0 doesn't contain recognizable data"); 31 | } 32 | }elseif(strlen($compressed) >= strlen($buffer)){ 33 | //this string is definitely compressible 34 | throw new \Error("Level $level didn't compress anything"); 35 | } 36 | } 37 | } 38 | ?> 39 | --EXPECT-- 40 | int(13) 41 | int(13) 42 | int(13) 43 | --------------------------------------------------------------------------------