├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config.m4 ├── crc16.c ├── php_phpiredis.h ├── php_phpiredis_struct.h ├── phpiredis.c ├── tests ├── client_001.phpt ├── client_002.phpt ├── client_003.phpt ├── client_004.phpt ├── client_005.phpt ├── client_006.phpt ├── client_007.phpt ├── client_008.phpt ├── client_009.phpt ├── client_010.phpt ├── client_011.phpt ├── reader_001.phpt ├── reader_002.phpt ├── reader_003.phpt ├── reader_004.phpt ├── reader_005.phpt ├── reader_006.phpt ├── reader_007.phpt ├── reader_008.phpt ├── reader_009.phpt ├── reader_010.phpt ├── reader_011.phpt ├── reader_012.phpt ├── reader_013.phpt ├── serializer_001.phpt ├── serializer_002.phpt ├── testsuite_configuration.inc ├── testsuite_skipif.inc ├── testsuite_utilities.inc └── utils_001.phpt └── travis_cflags.sh /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile* 2 | .* 3 | build 4 | ac*m4 5 | autom4te.cache 6 | config.guess 7 | config.h 8 | config.h.in 9 | config.log 10 | config.nice 11 | config.status 12 | config.sub 13 | configure 14 | configure.ac 15 | configure.in 16 | *.la 17 | *.lo 18 | *.so 19 | install-sh 20 | libtool 21 | ltmain.sh 22 | missing 23 | mkinstalldirs 24 | modules 25 | run-tests.php 26 | *~ 27 | phpiredis.loT 28 | tests/*.diff 29 | tests/*.exp 30 | tests/*.log 31 | tests/*.out 32 | tests/*.php 33 | tests/*.sh 34 | tmp-php.ini 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | - 7.0 9 | - 7.1.0alpha1 10 | 11 | sudo: required 12 | 13 | services: 14 | - redis-server 15 | 16 | env: 17 | global: 18 | - TEST_PHP_ARGS=-q 19 | 20 | install: 21 | - | 22 | git clone https://github.com/redis/hiredis.git \ 23 | && pushd hiredis \ 24 | && git checkout v0.13.3 \ 25 | && make \ 26 | && sudo make install \ 27 | && popd 28 | 29 | script: 30 | - export NO_INTERACTION=1 31 | - export REPORT_EXIT_STATUS=1 32 | - . ./travis_cflags.sh 33 | - phpize 34 | - ./configure --enable-phpiredis 35 | - make 36 | - export TEST_PHP_EXECUTABLE=`which php` 37 | - php run-tests.php -d extension=phpiredis.so -d extension_dir=modules -n ./*.phpt 38 | 39 | matrix: 40 | allow_failures: 41 | - php: 7.1.0alpha1 42 | fast_finish: true 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | PHPIREDIS CHANGELOG 2 | ================================================================================ 3 | 4 | v1.1.0 (2020-xx-xx) 5 | -------------------------------------------------------------------------------- 6 | 7 | * Phpiredis now builds on top of hiredis >= 0.14, >= 1.0. 8 | 9 | v1.0.1 (2020-09-20) 10 | -------------------------------------------------------------------------------- 11 | 12 | * Make Phpiredis compatible with PHP 8.0. 13 | 14 | v1.0.0 (2016-11-24) 15 | -------------------------------------------------------------------------------- 16 | 17 | * First stable release of phpiredis. 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2020, Daniele Alessandri 2 | Copyright (c) 2010-2012, Sebastian Waisbrot 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Phpiredis # 2 | 3 | [![Software license][ico-license]](LICENSE) 4 | [![Build status][ico-travis]][link-travis] 5 | 6 | Phpiredis is an extension for PHP 5.x to 8.x based on [hiredis](https://github.com/redis/hiredis) 7 | that provides a simple and efficient client for Redis and a fast incremental parser / serializer for 8 | the [RESP protocol](http://redis.io/topics/protocol). 9 | 10 | ## Installation ## 11 | 12 | Building and using this extension requires `hiredis` (>=0.14, >=1.0) to be installed on the system. 13 | `hiredis` is usually available in the repositories of most Linux distributions, alternatively it is 14 | possible to build it by fetching the code from its [repository](https://github.com/redis/hiredis). 15 | 16 | ```sh 17 | git clone https://github.com/nrk/phpiredis.git 18 | cd phpiredis 19 | phpize && ./configure --enable-phpiredis 20 | make && make install 21 | ``` 22 | 23 | When the configuration script is unable to locate `hiredis` on your system, you can specify in which 24 | directory it can be found using `--with-hiredis-dir=` (e.g. `--with-hiredis-dir=/usr/local`). 25 | 26 | Phpiredis provides a basic test suite that can be launched with `make test`. Tests require a running 27 | instance of `redis-server` listening on `127.0.0.1:6379` but __make sure__ that your server does not 28 | hold data you are interested: you could end up losing everything stored on it! 29 | 30 | If you notice a failing test or a bug, you can contribute by opening a pull request on GitHub or 31 | simply file a bug on our [issue tracker](http://github.com/nrk/phpiredis/issues). 32 | 33 | ## Usage ## 34 | 35 | Connecting to Redis is as simple as calling the `phpiredis_connect()` function with a server address 36 | as the first parameter and an optional port number when the server is listening to a different port 37 | than the default `6379`: 38 | 39 | ```php 40 | $redis = phpiredis_connect('127.0.0.1', 6379); // normal connection 41 | $redis = phpiredis_pconnect('127.0.0.1', 6379); // persistent connection 42 | ``` 43 | 44 | Alternatively you can connect to redis using UNIX domain socket connections. 45 | 46 | ```php 47 | $redis = phpiredis_connect('/tmp/redis.sock'); // normal connection 48 | $redis = phpiredis_pconnect('/tmp/redis.sock'); // persistent connection 49 | ``` 50 | 51 | Once the connection is established, you can send commands to Redis using `phpiredis_command_bs()` or 52 | pipeline them using `phpiredis_multi_command_bs()`: 53 | 54 | ```php 55 | $response = phpiredis_command_bs($redis, array('DEL', 'test')); 56 | 57 | $response = phpiredis_multi_command_bs($redis, array( 58 | array('SET', 'test', '1'), 59 | array('GET', 'test'), 60 | )); 61 | ``` 62 | 63 | The `_bs` suffix indicates that these functions can handle binary key names or values by using the 64 | unified Redis protocol available since Redis >= 1.2. 65 | 66 | Commands can still be sent using the old and deprecated inline protocol using `phpiredis_command()` 67 | and `phpiredis_multi_command()` (note the lack of the `_bs` suffix) but it's highly discouraged and 68 | these functions will be removed in future versions of phpiredis. 69 | 70 | ```php 71 | $response = phpiredis_command($redis, 'DEL test'); 72 | 73 | $response = phpiredis_multi_command($redis, array( 74 | 'SET test 1', 75 | 'GET test', 76 | )); 77 | ``` 78 | 79 | ## Contributing ## 80 | 81 | Any kind of contribution is extremely welcome! Just fork the project on GitHub, work on new features 82 | or bug fixes using feature branches and [open pull-requests](http://github.com/nrk/phpiredis/issues) 83 | with concise but complete descriptions of your changes. If you are unsure about a proposal, you can 84 | just open an issue to discuss it before writing actual code. 85 | 86 | ## Authors ## 87 | 88 | [Daniele Alessandri](https://github.com/nrk) (current maintainer) 89 | [Sebastian Waisbrot](https://github.com/seppo0010) (original developer) 90 | 91 | ## License ## 92 | 93 | The code for phpiredis is distributed under the terms of the BSD license (see [LICENSE](LICENSE)). 94 | 95 | [ico-license]: https://img.shields.io/github/license/nrk/phpiredis.svg?style=flat-square 96 | [ico-travis]: https://img.shields.io/travis/nrk/phpiredis.svg?style=flat-square 97 | 98 | [link-travis]: https://travis-ci.org/nrk/phpiredis 99 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | PHP_ARG_ENABLE(phpiredis, whether to enable phpiredis support, 2 | [ --enable-phpiredis Enable phpiredis support]) 3 | 4 | PHP_ARG_WITH(hiredis-dir, for hiredis library, 5 | [ --with-hiredis-dir[=DIR] Set the path to hiredis install prefix.], yes) 6 | 7 | if test "$PHP_PHPIREDIS" = "yes"; then 8 | 9 | 10 | AC_MSG_CHECKING([for hiredis installation]) 11 | 12 | # 13 | # Caller wants to check this path specifically 14 | # 15 | if test "x$PHP_HIREDIS_DIR" != "xno" && test "x$PHP_HIREDIS_DIR" != "xyes"; then 16 | if test -r "$PHP_HIREDIS_DIR/include/hiredis/hiredis.h"; then 17 | HIREDIS_DIR=$PHP_HIREDIS_DIR 18 | break 19 | fi 20 | else 21 | for i in /usr/local /usr /opt /opt/local; do 22 | if test -r "$i/include/hiredis/hiredis.h"; then 23 | HIREDIS_DIR=$i 24 | break 25 | fi 26 | done 27 | fi 28 | 29 | if test "x$HIREDIS_DIR" = "x"; then 30 | AC_MSG_ERROR([not found]) 31 | fi 32 | 33 | AC_MSG_RESULT([found in $HIREDIS_DIR]) 34 | 35 | PHP_ADD_LIBRARY_WITH_PATH(hiredis, [$HIREDIS_DIR/$PHP_LIBDIR], PHPIREDIS_SHARED_LIBADD) 36 | PHP_ADD_INCLUDE([$HIREDIS_DIR/include]) 37 | 38 | AC_DEFINE(HAVE_PHPIREDIS, 1, [Whether you have phpiredis]) 39 | PHP_SUBST(PHPIREDIS_SHARED_LIBADD) 40 | PHP_NEW_EXTENSION(phpiredis, phpiredis.c crc16.c, $ext_shared) 41 | fi 42 | -------------------------------------------------------------------------------- /crc16.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2001-2010 Georges Menie (www.menie.org) 3 | * Copyright 2010-2012 Salvatore Sanfilippo (adapted to Redis coding style) 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of the University of California, Berkeley nor the 15 | * names of its contributors may be used to endorse or promote products 16 | * derived from this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 19 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 22 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | /* CRC16 implementation according to CCITT standards. 31 | * 32 | * Note by @antirez: this is actually the XMODEM CRC 16 algorithm, using the 33 | * following parameters: 34 | * 35 | * Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN" 36 | * Width : 16 bit 37 | * Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1) 38 | * Initialization : 0000 39 | * Reflect Input byte : False 40 | * Reflect Output CRC : False 41 | * Xor constant to output CRC : 0000 42 | * Output for "123456789" : 31C3 43 | */ 44 | 45 | #include 46 | 47 | static const uint16_t crc16tab[256]= { 48 | 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, 49 | 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef, 50 | 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6, 51 | 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de, 52 | 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485, 53 | 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d, 54 | 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4, 55 | 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc, 56 | 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823, 57 | 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b, 58 | 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12, 59 | 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a, 60 | 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41, 61 | 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49, 62 | 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70, 63 | 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78, 64 | 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f, 65 | 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067, 66 | 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e, 67 | 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256, 68 | 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d, 69 | 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405, 70 | 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c, 71 | 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634, 72 | 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab, 73 | 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3, 74 | 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a, 75 | 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92, 76 | 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9, 77 | 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1, 78 | 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8, 79 | 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0 80 | }; 81 | 82 | uint16_t crc16(const char *buf, int len) { 83 | int counter; 84 | uint16_t crc = 0; 85 | for (counter = 0; counter < len; counter++) 86 | crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; 87 | return crc; 88 | } 89 | -------------------------------------------------------------------------------- /php_phpiredis.h: -------------------------------------------------------------------------------- 1 | #ifndef PHP_PHPIREDIS_H 2 | #define PHP_PHPIREDIS_H 3 | 4 | #ifdef HAVE_CONFIG_H 5 | #include "config.h" 6 | #endif 7 | 8 | #include 9 | 10 | uint16_t crc16(const char *buf, int len); 11 | 12 | #include "php.h" 13 | #include "php_ini.h" 14 | 15 | #define PHP_PHPIREDIS_VERSION "1.1.0-dev" 16 | #define PHP_PHPIREDIS_EXTNAME "phpiredis" 17 | 18 | PHP_MINIT_FUNCTION(phpiredis); 19 | PHP_FUNCTION(phpiredis_connect); 20 | PHP_FUNCTION(phpiredis_pconnect); 21 | PHP_FUNCTION(phpiredis_disconnect); 22 | PHP_FUNCTION(phpiredis_command_bs); 23 | PHP_FUNCTION(phpiredis_command); 24 | PHP_FUNCTION(phpiredis_multi_command); 25 | PHP_FUNCTION(phpiredis_multi_command_bs); 26 | PHP_FUNCTION(phpiredis_format_command); 27 | PHP_FUNCTION(phpiredis_reader_create); 28 | PHP_FUNCTION(phpiredis_reader_reset); 29 | PHP_FUNCTION(phpiredis_reader_feed); 30 | PHP_FUNCTION(phpiredis_reader_get_state); 31 | PHP_FUNCTION(phpiredis_reader_get_error); 32 | PHP_FUNCTION(phpiredis_reader_get_reply); 33 | PHP_FUNCTION(phpiredis_reader_destroy); 34 | PHP_FUNCTION(phpiredis_reader_set_error_handler); 35 | PHP_FUNCTION(phpiredis_reader_set_status_handler); 36 | PHP_FUNCTION(phpiredis_utils_crc16); 37 | 38 | extern zend_module_entry phpiredis_module_entry; 39 | #define phpext_phpiredis_ptr &phpiredis_module_entry 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /php_phpiredis_struct.h: -------------------------------------------------------------------------------- 1 | #include "hiredis/hiredis.h" 2 | 3 | typedef struct _phpiredis_connection { 4 | redisContext *ctx; 5 | char* ip; 6 | int port; 7 | zend_bool is_persistent; 8 | } phpiredis_connection; 9 | 10 | typedef struct _phpiredis_reader { 11 | void *reader; 12 | void *bufferedReply; 13 | char* error; 14 | void* status_callback; 15 | void* error_callback; 16 | } phpiredis_reader; 17 | 18 | #define PHPIREDIS_READER_STATE_COMPLETE 1 19 | #define PHPIREDIS_READER_STATE_INCOMPLETE 2 20 | #define PHPIREDIS_READER_STATE_ERROR 3 21 | -------------------------------------------------------------------------------- /phpiredis.c: -------------------------------------------------------------------------------- 1 | #include "hiredis/hiredis.h" 2 | #include "php_phpiredis.h" 3 | #include "php_phpiredis_struct.h" 4 | 5 | #include "ext/standard/head.h" 6 | #include "ext/standard/info.h" 7 | 8 | #define PHPIREDIS_CONNECTION_NAME "phpredis connection" 9 | #define PHPIREDIS_PERSISTENT_CONNECTION_NAME "phpredis connection persistent" 10 | #define PHPIREDIS_READER_NAME "phpredis reader" 11 | 12 | int le_redis_reader_context; 13 | int le_redis_context; 14 | int le_redis_persistent_context; 15 | 16 | #include 17 | 18 | #ifdef ZEND_ENGINE_3 19 | #define PHPIREDIS_LEN_TYPE size_t 20 | #define PHPIREDIS_RESOURCE_TYPE zend_resource 21 | #define PHPIREDIS_RETURN_RESOURCE(connection, context) \ 22 | RETURN_RES(zend_register_resource(connection, context)) 23 | #else 24 | #define PHPIREDIS_LEN_TYPE int 25 | #define PHPIREDIS_RESOURCE_TYPE zend_rsrc_list_entry 26 | #define PHPIREDIS_RETURN_RESOURCE(connection, context) \ 27 | ZEND_REGISTER_RESOURCE(return_value, connection, context) 28 | typedef long zend_long; 29 | #endif 30 | 31 | #ifndef TSRMLS_CC 32 | #define TSRMLS_CC 33 | #define TSRMLS_DC 34 | #endif 35 | 36 | typedef struct callback { 37 | #ifdef ZEND_ENGINE_3 38 | zval function; 39 | #else 40 | zval *function; 41 | #endif 42 | } callback; 43 | 44 | // -------------------------------------------------------------------------- // 45 | 46 | static void free_reader_status_callback(phpiredis_reader *reader TSRMLS_DC) 47 | { 48 | if (reader->status_callback) { 49 | zval_ptr_dtor(&((callback*) reader->status_callback)->function); 50 | efree(reader->status_callback); 51 | reader->status_callback = NULL; 52 | } 53 | } 54 | 55 | static void set_reader_status_callback(phpiredis_reader *reader, zval *function TSRMLS_DC) 56 | { 57 | free_reader_status_callback(reader TSRMLS_CC); 58 | 59 | reader->status_callback = emalloc(sizeof(callback)); 60 | 61 | #ifdef ZEND_ENGINE_3 62 | ZVAL_DUP(&((callback*) reader->status_callback)->function, function); 63 | #else 64 | Z_ADDREF_P(function); 65 | ((callback*) reader->status_callback)->function = function; 66 | #endif 67 | } 68 | 69 | static void free_reader_error_callback(phpiredis_reader *reader TSRMLS_DC) 70 | { 71 | if (reader->error_callback) { 72 | zval_ptr_dtor(&((callback*) reader->error_callback)->function); 73 | efree(reader->error_callback); 74 | reader->error_callback = NULL; 75 | } 76 | } 77 | 78 | static void set_reader_error_callback(phpiredis_reader *reader, zval *function TSRMLS_DC) 79 | { 80 | free_reader_error_callback(reader TSRMLS_CC); 81 | 82 | reader->error_callback = emalloc(sizeof(callback)); 83 | 84 | #ifdef ZEND_ENGINE_3 85 | ZVAL_DUP(&((callback*) reader->error_callback)->function, function); 86 | #else 87 | Z_ADDREF_P(function); 88 | ((callback*) reader->error_callback)->function = function; 89 | #endif 90 | } 91 | 92 | static void get_command_arguments(zval *arr, char ***elements, size_t **elementslen, int *size) 93 | { 94 | #ifdef ZEND_ENGINE_3 95 | zval *p_zv; 96 | #else 97 | HashPosition pos; 98 | zval **tmp; 99 | zval temp; 100 | #endif 101 | 102 | int currpos = 0; 103 | 104 | *size = zend_hash_num_elements(Z_ARRVAL_P(arr)); 105 | *elements = emalloc(sizeof(char*) * (*size)); 106 | *elementslen = emalloc(sizeof(size_t) * (*size)); 107 | 108 | #ifdef ZEND_ENGINE_3 109 | ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(arr), p_zv) { 110 | zend_string *str = zval_get_string(p_zv); 111 | 112 | (*elementslen)[currpos] = (size_t) str->len; 113 | (*elements)[currpos] = emalloc(sizeof(char) * (*elementslen)[currpos]); 114 | memcpy((*elements)[currpos], str->val, (*elementslen)[currpos]); 115 | 116 | ++currpos; 117 | 118 | zend_string_release(str); 119 | } ZEND_HASH_FOREACH_END(); 120 | #else 121 | zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(arr), &pos); 122 | while (zend_hash_get_current_data_ex(Z_ARRVAL_P(arr), (void **) &tmp, &pos) == SUCCESS) { 123 | temp = **tmp; 124 | zval_copy_ctor(&temp); 125 | convert_to_string(&temp); 126 | 127 | (*elementslen)[currpos] = (size_t) Z_STRLEN(temp); 128 | (*elements)[currpos] = emalloc(sizeof(char) * (*elementslen)[currpos]); 129 | memcpy((*elements)[currpos], Z_STRVAL(temp), (*elementslen)[currpos]); 130 | 131 | ++currpos; 132 | 133 | zval_dtor(&temp); 134 | zend_hash_move_forward_ex(Z_ARRVAL_P(arr), &pos); 135 | } 136 | #endif 137 | } 138 | 139 | static void free_command_arguments(char ***elements, size_t **elementslen, int *size) 140 | { 141 | for (; *size > 0; --*size) { 142 | efree((*elements)[*size-1]); 143 | } 144 | efree((*elements)); 145 | efree((*elementslen)); 146 | } 147 | 148 | static void convert_redis_to_php(phpiredis_reader *reader, zval *return_value, redisReply *reply TSRMLS_DC) 149 | { 150 | //int type; /* REDIS_REPLY_* */ 151 | //long long integer; /* The integer when type is REDIS_REPLY_INTEGER */ 152 | //int len; /* Length of string */ 153 | //char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */ 154 | //size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ 155 | //struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */ 156 | 157 | switch (reply->type) { 158 | case REDIS_REPLY_INTEGER: 159 | ZVAL_LONG(return_value, reply->integer); 160 | return; 161 | 162 | case REDIS_REPLY_ERROR: 163 | case REDIS_REPLY_STATUS: 164 | if (reader != NULL) { 165 | if (reply->type == REDIS_REPLY_ERROR && reader->error_callback != NULL) { 166 | #ifdef ZEND_ENGINE_3 167 | zval arg[1]; 168 | ZVAL_STRINGL(&arg[0], reply->str, reply->len); 169 | 170 | if (call_user_function(EG(function_table), NULL, &((callback*) reader->error_callback)->function, return_value, 1, arg TSRMLS_CC) == FAILURE) { 171 | zval_ptr_dtor(return_value); 172 | ZVAL_NULL(return_value); 173 | } 174 | #else 175 | zval *arg[1]; 176 | MAKE_STD_ZVAL(arg[0]); 177 | ZVAL_STRINGL(arg[0], reply->str, reply->len, 1); 178 | 179 | if (call_user_function(EG(function_table), NULL, ((callback*) reader->error_callback)->function, return_value, 1, arg TSRMLS_CC) == FAILURE) { 180 | zval_ptr_dtor(&return_value); 181 | ZVAL_NULL(return_value); 182 | } 183 | #endif 184 | zval_ptr_dtor(&arg[0]); 185 | 186 | return; 187 | } else if (reply->type == REDIS_REPLY_STATUS && reader->status_callback != NULL) { 188 | #ifdef ZEND_ENGINE_3 189 | zval arg[1]; 190 | ZVAL_STRINGL(&arg[0], reply->str, reply->len); 191 | 192 | if (call_user_function(EG(function_table), NULL, &((callback*) reader->status_callback)->function, return_value, 1, arg TSRMLS_CC) == FAILURE) { 193 | zval_ptr_dtor(return_value); 194 | ZVAL_NULL(return_value); 195 | } 196 | #else 197 | zval *arg[1]; 198 | MAKE_STD_ZVAL(arg[0]); 199 | ZVAL_STRINGL(arg[0], reply->str, reply->len, 1); 200 | 201 | if (call_user_function(EG(function_table), NULL, ((callback*) reader->status_callback)->function, return_value, 1, arg TSRMLS_CC) == FAILURE) { 202 | zval_ptr_dtor(&return_value); 203 | ZVAL_NULL(return_value); 204 | } 205 | #endif 206 | zval_ptr_dtor(&arg[0]); 207 | 208 | return; 209 | } 210 | } 211 | // NO BREAK! For status and error returning the string content 212 | 213 | case REDIS_REPLY_STRING: 214 | #ifdef ZEND_ENGINE_3 215 | ZVAL_STRINGL(return_value, reply->str, reply->len); 216 | #else 217 | ZVAL_STRINGL(return_value, reply->str, reply->len, 1); 218 | #endif 219 | return; 220 | 221 | case REDIS_REPLY_ARRAY: { 222 | #ifdef ZEND_ENGINE_3 223 | zval val; 224 | #else 225 | zval *val; 226 | #endif 227 | int j; 228 | 229 | array_init(return_value); 230 | for (j = 0; j < reply->elements; j++) { 231 | #ifdef ZEND_ENGINE_3 232 | convert_redis_to_php(reader, &val, reply->element[j] TSRMLS_CC); 233 | add_index_zval(return_value, j, &val); 234 | #else 235 | MAKE_STD_ZVAL(val); 236 | convert_redis_to_php(reader, val, reply->element[j] TSRMLS_CC); 237 | add_index_zval(return_value, j, val); 238 | #endif 239 | } 240 | } 241 | return; 242 | 243 | case REDIS_REPLY_NIL: 244 | default: 245 | ZVAL_NULL(return_value); 246 | return; 247 | } 248 | } 249 | 250 | static void get_pipeline_responses(phpiredis_connection *connection, zval *return_value, int commands TSRMLS_DC) 251 | { 252 | int i; 253 | 254 | for (i = 0; i < commands; ++i) { 255 | redisReply *reply = NULL; 256 | 257 | #ifdef ZEND_ENGINE_3 258 | zval result; 259 | zval *p_result = &result; 260 | #else 261 | zval *p_result; 262 | MAKE_STD_ZVAL(p_result); 263 | #endif 264 | 265 | if (redisGetReply(connection->ctx, (void *)&reply) != REDIS_OK) { 266 | for (; i < commands; ++i) { 267 | add_index_bool(return_value, i, 0); 268 | } 269 | 270 | if (reply) freeReplyObject(reply); 271 | #ifndef ZEND_ENGINE_3 272 | efree(p_result); 273 | #endif 274 | break; 275 | } 276 | 277 | convert_redis_to_php(NULL, p_result, reply TSRMLS_CC); 278 | add_index_zval(return_value, i, p_result); 279 | 280 | freeReplyObject(reply); 281 | } 282 | } 283 | 284 | static void s_destroy_connection(phpiredis_connection *connection TSRMLS_DC) 285 | { 286 | if (connection) { 287 | pefree(connection->ip, connection->is_persistent); 288 | if (connection->ctx != NULL) { 289 | redisFree(connection->ctx); 290 | } 291 | pefree(connection, connection->is_persistent); 292 | } 293 | } 294 | 295 | static void php_redis_connection_dtor(PHPIREDIS_RESOURCE_TYPE *rsrc TSRMLS_DC) 296 | { 297 | phpiredis_connection *connection = (phpiredis_connection*) rsrc->ptr; 298 | s_destroy_connection(connection TSRMLS_CC); 299 | } 300 | 301 | static void php_redis_reader_dtor(PHPIREDIS_RESOURCE_TYPE *rsrc TSRMLS_DC) 302 | { 303 | phpiredis_reader *reader = (void *) rsrc->ptr; 304 | 305 | if (reader) { 306 | if (reader->bufferedReply != NULL) { 307 | freeReplyObject(reader->bufferedReply); 308 | } 309 | 310 | if (reader->reader != NULL) { 311 | redisReaderFree(reader->reader); 312 | } 313 | 314 | free_reader_status_callback(reader TSRMLS_CC); 315 | free_reader_error_callback(reader TSRMLS_CC); 316 | 317 | efree(reader); 318 | } 319 | } 320 | 321 | static phpiredis_connection *fetch_resource_connection(zval *resource TSRMLS_DC) 322 | { 323 | phpiredis_connection *connection; 324 | 325 | #ifdef ZEND_ENGINE_3 326 | connection = (phpiredis_connection *)zend_fetch_resource2_ex(resource, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context); 327 | #else 328 | ZEND_FETCH_RESOURCE2_NO_RETURN(connection, phpiredis_connection *, &resource, -1, PHPIREDIS_CONNECTION_NAME, le_redis_context, le_redis_persistent_context); 329 | #endif 330 | 331 | return connection; 332 | } 333 | 334 | static phpiredis_reader *fetch_resource_reader(zval *resource TSRMLS_DC) 335 | { 336 | phpiredis_reader *reader; 337 | 338 | #ifdef ZEND_ENGINE_3 339 | reader = (phpiredis_reader *)zend_fetch_resource_ex(resource, PHPIREDIS_READER_NAME, le_redis_reader_context); 340 | #else 341 | ZEND_FETCH_RESOURCE_NO_RETURN(reader, phpiredis_reader *, &resource, -1, PHPIREDIS_READER_NAME, le_redis_reader_context); 342 | #endif 343 | 344 | return reader; 345 | } 346 | 347 | static phpiredis_connection *s_create_connection(const char *ip, int port, zend_long timeout, zend_bool is_persistent) 348 | { 349 | redisContext *ctx; 350 | phpiredis_connection *connection; 351 | 352 | if (timeout > 0) { 353 | struct timeval tv; 354 | 355 | tv.tv_sec = timeout / 1000; /* msec to sec */ 356 | tv.tv_usec = (timeout % 1000) * 1000; /* msec to usec */ 357 | 358 | if (ip[0] == '/') { 359 | ctx = redisConnectUnixWithTimeout(ip, tv); 360 | } else { 361 | ctx = redisConnectWithTimeout(ip, port, tv); 362 | } 363 | } else if (ip[0] == '/') { 364 | // We ignore the value of "port" if the string value in "ip" starts with 365 | // a slash character indicating a UNIX domain socket path. 366 | ctx = redisConnectUnix(ip); 367 | } else { 368 | ctx = redisConnect(ip, port); 369 | } 370 | 371 | if (!ctx || ctx->err) { 372 | redisFree(ctx); 373 | return NULL; 374 | } 375 | 376 | connection = pemalloc(sizeof(phpiredis_connection), is_persistent); 377 | connection->ctx = ctx; 378 | connection->ip = pestrdup(ip, is_persistent); 379 | connection->port = port; 380 | connection->is_persistent = is_persistent; 381 | 382 | return connection; 383 | } 384 | 385 | // -------------------------------------------------------------------------- // 386 | 387 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_connect, 0, 0, 1) 388 | ZEND_ARG_INFO(0, ip) 389 | ZEND_ARG_INFO(0, port) 390 | ZEND_ARG_INFO(0, timeout_ms) 391 | ZEND_END_ARG_INFO() 392 | 393 | PHP_FUNCTION(phpiredis_connect) 394 | { 395 | phpiredis_connection *connection; 396 | char *ip; 397 | PHPIREDIS_LEN_TYPE ip_size; 398 | zend_long port = 6379, timeout = 0; 399 | 400 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ll", &ip, &ip_size, &port, &timeout) == FAILURE) { 401 | return; 402 | } 403 | 404 | connection = s_create_connection(ip, port, timeout, 0); 405 | 406 | if (!connection) { 407 | RETURN_FALSE; 408 | } 409 | 410 | PHPIREDIS_RETURN_RESOURCE(connection, le_redis_context); 411 | } 412 | 413 | PHP_FUNCTION(phpiredis_pconnect) 414 | { 415 | char *ip; 416 | PHPIREDIS_LEN_TYPE ip_size; 417 | zend_long port = 6379, timeout; 418 | 419 | char *hashed_details = NULL; 420 | PHPIREDIS_LEN_TYPE hashed_details_length; 421 | phpiredis_connection *connection; 422 | #ifdef ZEND_ENGINE_3 423 | zval *p_zval; 424 | zend_resource new_le; 425 | #else 426 | zend_rsrc_list_entry new_le, *le; 427 | #endif 428 | 429 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ll", &ip, &ip_size, &port, &timeout) == FAILURE) { 430 | return; 431 | } 432 | 433 | hashed_details_length = spprintf(&hashed_details, 0, "phpiredis_%s_%d", ip, (int)port); 434 | 435 | #ifdef ZEND_ENGINE_3 436 | p_zval = zend_hash_str_find_ptr(&EG(persistent_list), hashed_details, hashed_details_length); 437 | 438 | if (p_zval != NULL) { 439 | if (Z_RES_P(p_zval)->type != le_redis_persistent_context) { 440 | RETURN_FALSE; 441 | } 442 | connection = (phpiredis_connection *) Z_RES_P(p_zval)->ptr; 443 | #else 444 | if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)!=FAILURE) { 445 | if (Z_TYPE_P(le) != le_redis_persistent_context) { 446 | RETURN_FALSE; 447 | } 448 | connection = (phpiredis_connection *) le->ptr; 449 | #endif 450 | efree(hashed_details); 451 | PHPIREDIS_RETURN_RESOURCE(connection, le_redis_persistent_context); 452 | return; 453 | } 454 | 455 | connection = s_create_connection(ip, port, timeout, 1); 456 | 457 | if (!connection) { 458 | efree(hashed_details); 459 | RETURN_FALSE; 460 | } 461 | 462 | new_le.type = le_redis_persistent_context; 463 | new_le.ptr = connection; 464 | 465 | #ifdef ZEND_ENGINE_3 466 | if (zend_hash_str_update_mem(&EG(persistent_list), hashed_details, hashed_details_length, &new_le, sizeof(zend_resource)) == NULL) { 467 | #else 468 | if (zend_hash_update(&EG(persistent_list), hashed_details, hashed_details_length+1, (void *) &new_le, sizeof(zend_rsrc_list_entry), NULL)==FAILURE) { 469 | #endif 470 | 471 | s_destroy_connection (connection TSRMLS_CC); 472 | efree(hashed_details); 473 | RETURN_FALSE; 474 | } 475 | 476 | efree(hashed_details); 477 | 478 | PHPIREDIS_RETURN_RESOURCE(connection, le_redis_persistent_context); 479 | } 480 | 481 | PHP_FUNCTION(phpiredis_disconnect) 482 | { 483 | zval *resource; 484 | 485 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &resource) == FAILURE) { 486 | RETURN_FALSE; 487 | } 488 | 489 | if (fetch_resource_connection(resource TSRMLS_CC) == NULL) { 490 | RETURN_FALSE; 491 | } 492 | 493 | #ifdef ZEND_ENGINE_3 494 | zend_list_close(Z_RES_P(resource)); 495 | #else 496 | zend_list_delete(Z_LVAL_P(resource)); 497 | #endif 498 | 499 | RETURN_TRUE; 500 | } 501 | 502 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_multi_command, 0, 0, 2) 503 | ZEND_ARG_INFO(0, connection) 504 | ZEND_ARG_ARRAY_INFO(0, commands, 0) 505 | ZEND_END_ARG_INFO() 506 | 507 | PHP_FUNCTION(phpiredis_multi_command) 508 | { 509 | zval *resource, *cmds; 510 | phpiredis_connection *connection; 511 | 512 | #ifdef ZEND_ENGINE_3 513 | zval *p_zval; 514 | #else 515 | HashPosition pos; 516 | zval **tmp; 517 | zval temp; 518 | #endif 519 | 520 | int commands = 0; 521 | 522 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ra", &resource, &cmds) == FAILURE) { 523 | return; 524 | } 525 | 526 | connection = fetch_resource_connection(resource TSRMLS_CC); 527 | if (connection == NULL) { 528 | RETURN_FALSE; 529 | } 530 | 531 | #ifdef ZEND_ENGINE_3 532 | ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cmds), p_zval) { 533 | redisAppendCommand(connection->ctx, Z_STRVAL_P(p_zval)); 534 | 535 | ++commands; 536 | } ZEND_HASH_FOREACH_END(); 537 | #else 538 | zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(cmds), &pos); 539 | while (zend_hash_get_current_data_ex(Z_ARRVAL_P(cmds), (void **) &tmp, &pos) == SUCCESS) { 540 | temp = **tmp; 541 | zval_copy_ctor(&temp); 542 | convert_to_string(&temp); 543 | 544 | redisAppendCommand(connection->ctx, Z_STRVAL(temp)); 545 | 546 | ++commands; 547 | 548 | zval_dtor(&temp); 549 | zend_hash_move_forward_ex(Z_ARRVAL_P(cmds), &pos); 550 | } 551 | #endif 552 | 553 | array_init(return_value); 554 | get_pipeline_responses(connection, return_value, commands TSRMLS_CC); 555 | } 556 | 557 | PHP_FUNCTION(phpiredis_multi_command_bs) 558 | { 559 | zval *resource, *cmds; 560 | phpiredis_connection *connection; 561 | zval *p_cmdArgs; 562 | #ifndef ZEND_ENGINE_3 563 | zval cmdArgs; 564 | HashPosition cmdsPos; 565 | zval **tmp; 566 | #endif 567 | int cmdSize; 568 | char **cmdElements; 569 | size_t *cmdElementslen; 570 | 571 | int commands; 572 | 573 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ra", &resource, &cmds) == FAILURE) { 574 | return; 575 | } 576 | 577 | connection = fetch_resource_connection(resource TSRMLS_CC); 578 | if (connection == NULL) { 579 | RETURN_FALSE; 580 | } 581 | 582 | commands = 0; 583 | 584 | #ifdef ZEND_ENGINE_3 585 | ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(cmds), p_cmdArgs) { 586 | #else 587 | 588 | zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(cmds), &cmdsPos); 589 | while (zend_hash_get_current_data_ex(Z_ARRVAL_P(cmds), (void **) &tmp, &cmdsPos) == SUCCESS) { 590 | cmdArgs = **tmp; 591 | p_cmdArgs = &cmdArgs; 592 | zval_copy_ctor(p_cmdArgs); 593 | #endif 594 | get_command_arguments(p_cmdArgs, &cmdElements, &cmdElementslen, &cmdSize); 595 | redisAppendCommandArgv(connection->ctx, cmdSize, (const char **)cmdElements, cmdElementslen); 596 | 597 | free_command_arguments(&cmdElements, &cmdElementslen, &cmdSize); 598 | 599 | ++commands; 600 | 601 | #ifdef ZEND_ENGINE_3 602 | } ZEND_HASH_FOREACH_END(); 603 | #else 604 | zval_dtor(&cmdArgs); 605 | zend_hash_move_forward_ex(Z_ARRVAL_P(cmds), &cmdsPos); 606 | } 607 | #endif 608 | 609 | array_init(return_value); 610 | get_pipeline_responses(connection, return_value, commands TSRMLS_CC); 611 | } 612 | 613 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_command, 0, 0, 2) 614 | ZEND_ARG_INFO(0, connection) 615 | ZEND_ARG_INFO(0, command) 616 | ZEND_END_ARG_INFO() 617 | 618 | PHP_FUNCTION(phpiredis_command) 619 | { 620 | zval *resource; 621 | redisReply *reply = NULL; 622 | phpiredis_connection *connection; 623 | char *command; 624 | PHPIREDIS_LEN_TYPE command_size; 625 | 626 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &resource, &command, &command_size) == FAILURE) { 627 | return; 628 | } 629 | 630 | connection = fetch_resource_connection(resource TSRMLS_CC); 631 | if (connection == NULL) { 632 | RETURN_FALSE; 633 | } 634 | 635 | reply = redisCommand(connection->ctx, command); 636 | if (reply == NULL) { 637 | RETURN_FALSE; 638 | } 639 | 640 | if (reply->type == REDIS_REPLY_ERROR) { 641 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", reply->str); 642 | freeReplyObject(reply); 643 | 644 | RETURN_FALSE; 645 | } 646 | 647 | convert_redis_to_php(NULL, return_value, reply TSRMLS_CC); 648 | freeReplyObject(reply); 649 | } 650 | 651 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_command_bs, 0, 0, 2) 652 | ZEND_ARG_INFO(0, connection) 653 | ZEND_ARG_ARRAY_INFO(0, args, 0) 654 | ZEND_END_ARG_INFO() 655 | 656 | PHP_FUNCTION(phpiredis_command_bs) 657 | { 658 | zval *resource; 659 | zval *cmdArgs; 660 | phpiredis_connection *connection; 661 | 662 | redisReply *reply = NULL; 663 | 664 | int cmdSize; 665 | char **cmdElements; 666 | size_t *cmdElementslen; 667 | 668 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ra", &resource, &cmdArgs) == FAILURE) { 669 | return; 670 | } 671 | 672 | connection = fetch_resource_connection(resource TSRMLS_CC); 673 | if (connection == NULL) { 674 | RETURN_FALSE; 675 | } 676 | 677 | get_command_arguments(cmdArgs, &cmdElements, &cmdElementslen, &cmdSize); 678 | redisAppendCommandArgv(connection->ctx, cmdSize, (const char **)cmdElements, cmdElementslen); 679 | 680 | free_command_arguments(&cmdElements, &cmdElementslen, &cmdSize); 681 | 682 | if (redisGetReply(connection->ctx, (void **)&reply) != REDIS_OK) { 683 | // only free if the reply was actually created 684 | if (reply) freeReplyObject(reply); 685 | 686 | RETURN_FALSE; 687 | } 688 | 689 | if (reply->type == REDIS_REPLY_ERROR) { 690 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", reply->str); 691 | freeReplyObject(reply); 692 | 693 | RETURN_FALSE; 694 | } 695 | 696 | convert_redis_to_php(NULL, return_value, reply TSRMLS_CC); 697 | 698 | freeReplyObject(reply); 699 | } 700 | 701 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_format_command, 0, 0, 1) 702 | ZEND_ARG_ARRAY_INFO(0, args, 0) 703 | ZEND_END_ARG_INFO() 704 | 705 | PHP_FUNCTION(phpiredis_format_command) 706 | { 707 | zval *cmdArgs; 708 | 709 | char *cmd; 710 | int cmdlen; 711 | int cmdSize; 712 | char **cmdElements; 713 | size_t *cmdElementslen; 714 | 715 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &cmdArgs) == FAILURE) { 716 | return; 717 | } 718 | 719 | get_command_arguments(cmdArgs, &cmdElements, &cmdElementslen, &cmdSize); 720 | cmdlen = redisFormatCommandArgv(&cmd, cmdSize, (const char **)cmdElements, cmdElementslen); 721 | 722 | #ifdef ZEND_ENGINE_3 723 | ZVAL_STRINGL(return_value, cmd, cmdlen); 724 | #else 725 | ZVAL_STRINGL(return_value, cmd, cmdlen, 1); 726 | #endif 727 | 728 | free_command_arguments(&cmdElements, &cmdElementslen, &cmdSize); 729 | free(cmd); 730 | } 731 | 732 | PHP_FUNCTION(phpiredis_reader_create) 733 | { 734 | phpiredis_reader *reader; 735 | 736 | if (zend_parse_parameters_none() == FAILURE) { 737 | RETURN_FALSE; 738 | } 739 | 740 | reader = emalloc(sizeof(phpiredis_reader)); 741 | reader->reader = redisReaderCreate(); 742 | reader->error = NULL; 743 | reader->bufferedReply = NULL; 744 | reader->status_callback = NULL; 745 | reader->error_callback = NULL; 746 | 747 | PHPIREDIS_RETURN_RESOURCE(reader, le_redis_reader_context); 748 | } 749 | 750 | PHP_FUNCTION(phpiredis_reader_set_status_handler) 751 | { 752 | zval *resource, *function = NULL; 753 | phpiredis_reader *reader; 754 | 755 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &resource, &function) == FAILURE) { 756 | return; 757 | } 758 | 759 | reader = fetch_resource_reader(resource TSRMLS_CC); 760 | if (reader == NULL) { 761 | RETURN_FALSE; 762 | } 763 | 764 | #ifdef ZEND_ENGINE_3 765 | ZVAL_DEREF(function); 766 | #endif 767 | 768 | if (Z_TYPE_P(function) == IS_NULL) { 769 | free_reader_status_callback(reader TSRMLS_CC); 770 | } else { 771 | if (!zend_is_callable(function, 0, NULL TSRMLS_CC)) { 772 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument is not a valid callback"); 773 | RETURN_FALSE; 774 | } 775 | 776 | set_reader_status_callback(reader, function TSRMLS_CC); 777 | } 778 | 779 | RETURN_TRUE; 780 | } 781 | 782 | PHP_FUNCTION(phpiredis_reader_set_error_handler) 783 | { 784 | zval *resource, *function = NULL; 785 | phpiredis_reader *reader; 786 | 787 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rz", &resource, &function) == FAILURE) { 788 | return; 789 | } 790 | 791 | reader = fetch_resource_reader(resource TSRMLS_CC); 792 | if (reader == NULL) { 793 | RETURN_FALSE; 794 | } 795 | 796 | #ifdef ZEND_ENGINE_3 797 | ZVAL_DEREF(function); 798 | #endif 799 | 800 | if (Z_TYPE_P(function) == IS_NULL) { 801 | free_reader_error_callback(reader TSRMLS_CC); 802 | } else { 803 | if (!zend_is_callable(function, 0, NULL TSRMLS_CC)) { 804 | php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument is not a valid callback"); 805 | RETURN_FALSE; 806 | } 807 | 808 | set_reader_error_callback(reader, function TSRMLS_CC); 809 | } 810 | 811 | RETURN_TRUE; 812 | } 813 | 814 | PHP_FUNCTION(phpiredis_reader_reset) 815 | { 816 | zval *resource; 817 | phpiredis_reader *reader; 818 | 819 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &resource) == FAILURE) { 820 | return; 821 | } 822 | 823 | reader = fetch_resource_reader(resource TSRMLS_CC); 824 | if (reader == NULL) { 825 | return; 826 | } 827 | 828 | if (reader->bufferedReply != NULL) { 829 | freeReplyObject(reader->bufferedReply); 830 | reader->bufferedReply = NULL; 831 | } 832 | 833 | if (reader->reader != NULL) { 834 | redisReaderFree(reader->reader); 835 | } 836 | 837 | reader->reader = redisReaderCreate(); 838 | } 839 | 840 | PHP_FUNCTION(phpiredis_reader_destroy) 841 | { 842 | zval *resource; 843 | phpiredis_reader *reader; 844 | 845 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &resource) == FAILURE) { 846 | return; 847 | } 848 | 849 | reader = fetch_resource_reader(resource TSRMLS_CC); 850 | if (reader == NULL) { 851 | RETURN_FALSE; 852 | } 853 | 854 | #ifdef ZEND_ENGINE_3 855 | zend_list_close(Z_RES_P(resource)); 856 | #else 857 | zend_list_delete(Z_LVAL_P(resource)); 858 | #endif 859 | 860 | RETURN_TRUE; 861 | } 862 | 863 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_reader_feed, 0, 0, 2) 864 | ZEND_ARG_INFO(0, connection) 865 | ZEND_ARG_INFO(0, buffer) 866 | ZEND_END_ARG_INFO() 867 | 868 | PHP_FUNCTION(phpiredis_reader_feed) 869 | { 870 | zval *resource; 871 | phpiredis_reader *reader; 872 | char *bytes; 873 | PHPIREDIS_LEN_TYPE size; 874 | 875 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs", &resource, &bytes, &size) == FAILURE) { 876 | return; 877 | } 878 | 879 | reader = fetch_resource_reader(resource TSRMLS_CC); 880 | if (reader == NULL) { 881 | RETURN_FALSE; 882 | } 883 | 884 | redisReaderFeed(reader->reader, bytes, size); 885 | } 886 | 887 | PHP_FUNCTION(phpiredis_reader_get_error) 888 | { 889 | zval *resource; 890 | phpiredis_reader *reader; 891 | 892 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &resource) == FAILURE) { 893 | return; 894 | } 895 | 896 | reader = fetch_resource_reader(resource TSRMLS_CC); 897 | if (reader == NULL) { 898 | RETURN_FALSE; 899 | } 900 | 901 | if (reader->error == NULL) { 902 | RETURN_FALSE; 903 | } 904 | 905 | #ifdef ZEND_ENGINE_3 906 | ZVAL_STRINGL(return_value, reader->error, strlen(reader->error)); 907 | #else 908 | ZVAL_STRINGL(return_value, reader->error, strlen(reader->error), 1); 909 | #endif 910 | } 911 | 912 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_reader_get_reply, 0, 0, 1) 913 | ZEND_ARG_INFO(0, ptr) 914 | ZEND_ARG_INFO(1, type) 915 | ZEND_END_ARG_INFO() 916 | 917 | PHP_FUNCTION(phpiredis_reader_get_reply) 918 | { 919 | zval *resource, *replyType = NULL; 920 | phpiredis_reader *reader; 921 | redisReply *aux; 922 | 923 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|z/", &resource, &replyType) == FAILURE) { 924 | return; 925 | } 926 | 927 | reader = fetch_resource_reader(resource TSRMLS_CC); 928 | if (reader == NULL) { 929 | RETURN_FALSE; 930 | } 931 | 932 | if (reader->bufferedReply) { 933 | aux = reader->bufferedReply; 934 | reader->bufferedReply = NULL; 935 | } else { 936 | if (redisReaderGetReply(reader->reader, (void **)&aux) == REDIS_ERR) { 937 | if (reader->error != NULL) { 938 | efree(reader->error); 939 | } 940 | reader->error = redisReaderGetError(reader->reader); 941 | 942 | RETURN_FALSE; // error 943 | } else if (aux == NULL) { 944 | RETURN_FALSE; // incomplete 945 | } 946 | } 947 | 948 | convert_redis_to_php(reader, return_value, aux TSRMLS_CC); 949 | 950 | if (ZEND_NUM_ARGS() > 1) { 951 | zval_dtor(replyType); 952 | ZVAL_LONG(replyType, aux->type); 953 | } 954 | 955 | freeReplyObject(aux); 956 | } 957 | 958 | PHP_FUNCTION(phpiredis_reader_get_state) 959 | { 960 | zval *resource; 961 | phpiredis_reader *reader; 962 | 963 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &resource) == FAILURE) { 964 | return; 965 | } 966 | 967 | reader = fetch_resource_reader(resource TSRMLS_CC); 968 | if (reader == NULL) { 969 | RETURN_FALSE; 970 | } 971 | 972 | if (reader->error == NULL && reader->bufferedReply == NULL) { 973 | void *aux; 974 | 975 | if (redisReaderGetReply(reader->reader, &aux) == REDIS_ERR) { 976 | if (reader->error != NULL) { 977 | efree(reader->error); 978 | } 979 | reader->error = redisReaderGetError(reader->reader); 980 | } else { 981 | reader->bufferedReply = aux; 982 | } 983 | } 984 | 985 | if (reader->error != NULL) { 986 | ZVAL_LONG(return_value, PHPIREDIS_READER_STATE_ERROR); 987 | } else if (reader->bufferedReply != NULL) { 988 | ZVAL_LONG(return_value, PHPIREDIS_READER_STATE_COMPLETE); 989 | } else { 990 | ZVAL_LONG(return_value, PHPIREDIS_READER_STATE_INCOMPLETE); 991 | } 992 | } 993 | 994 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_utils_crc16, 0, 0, 1) 995 | ZEND_ARG_INFO(0, buffer) 996 | ZEND_END_ARG_INFO() 997 | 998 | PHP_FUNCTION(phpiredis_utils_crc16) 999 | { 1000 | char *buf; 1001 | PHPIREDIS_LEN_TYPE buf_size; 1002 | uint16_t crc; 1003 | 1004 | if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &buf, &buf_size) == FAILURE) { 1005 | return; 1006 | } 1007 | 1008 | crc = crc16(buf, buf_size); 1009 | 1010 | ZVAL_LONG(return_value, crc); 1011 | } 1012 | 1013 | // -------------------------------------------------------------------------- // 1014 | 1015 | PHP_MINIT_FUNCTION(phpiredis) 1016 | { 1017 | le_redis_context = zend_register_list_destructors_ex(php_redis_connection_dtor, NULL, PHPIREDIS_CONNECTION_NAME, module_number); 1018 | le_redis_persistent_context = zend_register_list_destructors_ex(NULL, php_redis_connection_dtor, PHPIREDIS_PERSISTENT_CONNECTION_NAME, module_number); 1019 | le_redis_reader_context = zend_register_list_destructors_ex(php_redis_reader_dtor, NULL, PHPIREDIS_READER_NAME, module_number); 1020 | 1021 | REGISTER_LONG_CONSTANT("PHPIREDIS_READER_STATE_INCOMPLETE", PHPIREDIS_READER_STATE_INCOMPLETE, CONST_PERSISTENT|CONST_CS); 1022 | REGISTER_LONG_CONSTANT("PHPIREDIS_READER_STATE_COMPLETE", PHPIREDIS_READER_STATE_COMPLETE, CONST_PERSISTENT|CONST_CS); 1023 | REGISTER_LONG_CONSTANT("PHPIREDIS_READER_STATE_ERROR", PHPIREDIS_READER_STATE_ERROR, CONST_PERSISTENT|CONST_CS); 1024 | REGISTER_LONG_CONSTANT("PHPIREDIS_REPLY_STRING", REDIS_REPLY_STRING, CONST_PERSISTENT|CONST_CS); 1025 | REGISTER_LONG_CONSTANT("PHPIREDIS_REPLY_ARRAY", REDIS_REPLY_ARRAY, CONST_PERSISTENT|CONST_CS); 1026 | REGISTER_LONG_CONSTANT("PHPIREDIS_REPLY_INTEGER", REDIS_REPLY_INTEGER, CONST_PERSISTENT|CONST_CS); 1027 | REGISTER_LONG_CONSTANT("PHPIREDIS_REPLY_NIL", REDIS_REPLY_NIL, CONST_PERSISTENT|CONST_CS); 1028 | REGISTER_LONG_CONSTANT("PHPIREDIS_REPLY_STATUS", REDIS_REPLY_STATUS, CONST_PERSISTENT|CONST_CS); 1029 | REGISTER_LONG_CONSTANT("PHPIREDIS_REPLY_ERROR", REDIS_REPLY_ERROR, CONST_PERSISTENT|CONST_CS); 1030 | 1031 | return SUCCESS; 1032 | } 1033 | 1034 | /* arginfo shared by various functions */ 1035 | 1036 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_void, 0, 0, 0) 1037 | ZEND_END_ARG_INFO() 1038 | 1039 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_conn, 0, 0, 1) 1040 | ZEND_ARG_INFO(0, connection) 1041 | ZEND_END_ARG_INFO() 1042 | 1043 | ZEND_BEGIN_ARG_INFO_EX(arginfo_phpiredis_callback, 0, 0, 2) 1044 | ZEND_ARG_INFO(0, connection) 1045 | ZEND_ARG_INFO(0, callback) 1046 | ZEND_END_ARG_INFO() 1047 | 1048 | 1049 | static zend_function_entry phpiredis_functions[] = { 1050 | PHP_FE(phpiredis_connect, arginfo_phpiredis_connect) 1051 | PHP_FE(phpiredis_pconnect, arginfo_phpiredis_connect) 1052 | PHP_FE(phpiredis_disconnect, arginfo_phpiredis_conn) 1053 | PHP_FE(phpiredis_command, arginfo_phpiredis_command) 1054 | PHP_FE(phpiredis_command_bs, arginfo_phpiredis_command_bs) 1055 | PHP_FE(phpiredis_multi_command, arginfo_phpiredis_multi_command) 1056 | PHP_FE(phpiredis_multi_command_bs, arginfo_phpiredis_multi_command) 1057 | PHP_FE(phpiredis_format_command, arginfo_phpiredis_format_command) 1058 | PHP_FE(phpiredis_reader_create, arginfo_phpiredis_void) 1059 | PHP_FE(phpiredis_reader_reset, arginfo_phpiredis_conn) 1060 | PHP_FE(phpiredis_reader_feed, arginfo_phpiredis_reader_feed) 1061 | PHP_FE(phpiredis_reader_get_state, arginfo_phpiredis_conn) 1062 | PHP_FE(phpiredis_reader_get_error, arginfo_phpiredis_conn) 1063 | PHP_FE(phpiredis_reader_get_reply, arginfo_phpiredis_reader_get_reply) 1064 | PHP_FE(phpiredis_reader_destroy, arginfo_phpiredis_conn) 1065 | PHP_FE(phpiredis_reader_set_error_handler, arginfo_phpiredis_callback) 1066 | PHP_FE(phpiredis_reader_set_status_handler, arginfo_phpiredis_callback) 1067 | PHP_FE(phpiredis_utils_crc16, arginfo_phpiredis_utils_crc16) 1068 | #ifdef PHP_FE_END 1069 | PHP_FE_END 1070 | #else 1071 | {NULL, NULL, NULL} 1072 | #endif 1073 | }; 1074 | 1075 | static PHP_MINFO_FUNCTION(phpiredis) 1076 | { 1077 | char buf[32]; 1078 | 1079 | php_info_print_table_start(); 1080 | 1081 | php_info_print_table_row(2, "phpiredis", "enabled"); 1082 | php_info_print_table_row(2, "phpiredis version", PHP_PHPIREDIS_VERSION); 1083 | snprintf(buf, sizeof(buf), "%d.%d.%d", HIREDIS_MAJOR, HIREDIS_MINOR, HIREDIS_PATCH); 1084 | php_info_print_table_row(2, "hiredis version", buf); 1085 | 1086 | php_info_print_table_end(); 1087 | } 1088 | 1089 | zend_module_entry phpiredis_module_entry = { 1090 | #if ZEND_MODULE_API_NO >= 20010901 1091 | STANDARD_MODULE_HEADER, 1092 | #endif 1093 | PHP_PHPIREDIS_EXTNAME, 1094 | phpiredis_functions, 1095 | PHP_MINIT(phpiredis), 1096 | NULL, 1097 | NULL, 1098 | NULL, 1099 | PHP_MINFO(phpiredis), 1100 | #if ZEND_MODULE_API_NO >= 20010901 1101 | PHP_PHPIREDIS_VERSION, 1102 | #endif 1103 | STANDARD_MODULE_PROPERTIES 1104 | }; 1105 | 1106 | #ifdef COMPILE_DL_PHPIREDIS 1107 | ZEND_GET_MODULE(phpiredis) 1108 | #endif 1109 | -------------------------------------------------------------------------------- /tests/client_001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [CLIENT] Connect to Redis 3 | 4 | --SKIPIF-- 5 | 7 | --FILE-- 8 | 21 | --EXPECT-- 22 | string(1) "1" 23 | -------------------------------------------------------------------------------- /tests/client_006.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [CLIENT] Execute pipelined commands returning nested multibulk responses 3 | 4 | --SKIPIF-- 5 | 27 | string(1) "3" 28 | [1]=> 29 | string(1) "2" 30 | [2]=> 31 | string(1) "1" 32 | } 33 | -------------------------------------------------------------------------------- /tests/client_007.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [CLIENT] Execute commands (binary-safe) 3 | 4 | --SKIPIF-- 5 | 24 | string(1) "1" 25 | [1]=> 26 | string(2) "OK" 27 | [2]=> 28 | bool(false) 29 | } 30 | -------------------------------------------------------------------------------- /tests/reader_001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [READER] Create reader resource 3 | 4 | --SKIPIF-- 5 | 39 | string(3) "SET" 40 | [1]=> 41 | string(1) "a" 42 | [2]=> 43 | string(2) "AS" 44 | } 45 | bool(false) 46 | string(42) "Protocol error, got "n" as reply type byte" 47 | -------------------------------------------------------------------------------- /tests/reader_003.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [READER] Check reader state 3 | 4 | --SKIPIF-- 5 | err = $err; 18 | return $r; 19 | } 20 | 21 | $reader = phpiredis_reader_create(); 22 | 23 | phpiredis_reader_set_error_handler($reader, 'error_handler_exception'); 24 | phpiredis_reader_feed($reader, "-ERR\r\n"); 25 | 26 | try { 27 | phpiredis_reader_get_reply($reader); 28 | var_dump(FALSE); 29 | } catch (Exception $e) { 30 | var_dump($e->getMessage() == 'ERR'); 31 | } 32 | 33 | phpiredis_reader_set_error_handler($reader, 'error_handler_object'); 34 | phpiredis_reader_feed($reader, "-ERR\r\n"); 35 | var_dump(phpiredis_reader_get_reply($reader)); 36 | 37 | --EXPECT-- 38 | bool(true) 39 | object(stdClass)#2 (1) { 40 | ["err"]=> 41 | string(3) "ERR" 42 | } 43 | -------------------------------------------------------------------------------- /tests/reader_009.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [READER] Properly handle NULL responses 3 | 4 | --SKIPIF-- 5 | 30 | NULL 31 | } 32 | -------------------------------------------------------------------------------- /tests/reader_010.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [READER] Keep multibulk responses types 3 | 4 | --SKIPIF-- 5 | 19 | int(1) 20 | [1]=> 21 | string(2) "OK" 22 | [2]=> 23 | array(2) { 24 | [0]=> 25 | string(3) "ASD" 26 | [1]=> 27 | NULL 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/reader_011.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [READER] Test regression for segfaults 3 | 4 | --SKIPIF-- 5 | 29 | int(1) 30 | } 31 | -------------------------------------------------------------------------------- /tests/reader_012.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [READER] Parameters formatting should not modify input argument. 3 | 4 | --SKIPIF-- 5 | 26 | string(3) "SET" 27 | [1]=> 28 | string(3) "key" 29 | [2]=> 30 | int(1) 31 | } 32 | -------------------------------------------------------------------------------- /tests/reader_013.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [READER] Parameters formatting should not break on many arguments 3 | 4 | --SKIPIF-- 5 | 42 | string(4) "MSET" 43 | [1]=> 44 | string(4) "key1" 45 | [2]=> 46 | string(1) "1" 47 | [3]=> 48 | string(4) "key2" 49 | [4]=> 50 | string(1) "2" 51 | [5]=> 52 | string(4) "key3" 53 | [6]=> 54 | string(1) "3" 55 | [7]=> 56 | string(4) "key4" 57 | [8]=> 58 | string(1) "4" 59 | [9]=> 60 | string(4) "key5" 61 | [10]=> 62 | string(1) "5" 63 | } 64 | -------------------------------------------------------------------------------- /tests/serializer_001.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | [SERIALIZER] Command serialization 3 | 4 | --SKIPIF-- 5 |