├── .gitignore ├── README.md ├── config.m4 ├── tutorial.c └── tutorial.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.lo 2 | *.la 3 | .*.swp 4 | .deps 5 | .libs/ 6 | include/ 7 | tests/*.diff 8 | tests/*.exp 9 | tests/*.log 10 | tests/*.php 11 | tests/*.out 12 | tests/*.sh 13 | Makefile 14 | Makefile.fragments 15 | Makefile.global 16 | Makefile.objects 17 | acinclude.m4 18 | aclocal.m4 19 | autom4te.cache/ 20 | build/ 21 | config.guess 22 | config.h 23 | config.h.in 24 | config.log 25 | config.nice 26 | config.status 27 | config.sub 28 | configure 29 | configure.ac 30 | configure.in 31 | install-sh 32 | libtool 33 | ltmain.sh 34 | missing 35 | mkinstalldirs 36 | modules/ 37 | run-tests.php 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Extension Writing Tutorial 2 | 3 | ## Prerequisites 4 | 5 | Before beginning this tutorial, you'll need a development environment with the following packages: 6 | 7 | * Compiler: `gcc` and `clang` are both excellent choices. 8 | * Build tools: `autoconf`, `automake`, `libtool` 9 | * (Optional) parser generators: `re2c` and `bison` 10 | * PHP 7.x with development headers: 11 | * Either distro packages such as: `php7` and `php7-devel` 12 | * PHP compiled from source and installed with `sudo make install`. If compiling from source, then the `--enable-debug` switch is recommended, as well as the optional re2c and bison packages. 13 | * `libcurl` and development headers: As part of this tutorial, we'll eventually be linking against libcurl. Install the library and it's associated development headers from your distro or install from source. 14 | 15 | 16 | Obviously, you'll want this repo checked out in your build environment as well. 17 | Be sure to `git pull` to make sure you have the most recent version. 18 | 19 | ## Format 20 | 21 | This tutorial is split into steps, with one commit per step. 22 | At each commit, you should be able to compile the extension code 23 | as-provided using the following steps: 24 | 25 | ### Generate ./configure script using phpize 26 | 27 | ```sh 28 | $ phpize 29 | Configuring for: 30 | PHP Api Version: 20170718 31 | Zend Module Api No: 20170718 32 | Zend Extension Api No: 320170718 33 | ``` 34 | 35 | ### Generate Makefile using ./configure 36 | 37 | ```sh 38 | $ ./configure 39 | checking ... 40 | checking ... 41 | checking ... 42 | [ lots of output ] 43 | configure: creating ./config.status 44 | config.status: creating config.h 45 | ``` 46 | 47 | ### Build your extension 48 | 49 | ```sh 50 | $ make 51 | /bin/bash .../libtool --mode=compile cc -I. -DPHP_ATOM_INC -I/usr/local/include/php -I/usr/local/include/php/main -I/usr/local/include/php/TSRM -I/usr/local/include/php/Zend -I/usr/local/include/php/ext -DHAVE_CONFIG_H -g -O0 -c tutorial.c modules/tutorial.so 52 | 53 | ---------------------------------------------------------------------- 54 | Libraries have been installed in: 55 | .../modules 56 | 57 | Build complete. 58 | Don't forget to run 'make test'. 59 | ``` 60 | 61 | ### Check to be sure it loads 62 | 63 | ```sh 64 | $ php -d extension=modules/tutorial.so --re tutorial 65 | Extension [ extension #40 tutorial version ] { 66 | } 67 | ``` 68 | 69 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl config.m4 2 | PHP_ARG_WITH(tutorial) 3 | 4 | if test "$PHP_TUTORIAL" != "no"; then 5 | 6 | dnl Check for required libcurl library 7 | AC_MSG_CHECKING([for libcurl]) 8 | for i in $PHP_TUTORIAL /usr/local /usr; do 9 | if test -f "$i/include/curl/easy.h"; then 10 | PHP_CURL_DIR=$i 11 | break 12 | fi 13 | done 14 | if test -z "$PHP_CURL_DIR"; then 15 | AC_MSG_ERROR([not found]) 16 | fi 17 | 18 | dnl Found libcurl's headers 19 | AC_MSG_RESULT([found in $PHP_CURL_DIR]) 20 | 21 | dnl Update library list and include paths for libcurl 22 | PHP_ADD_INCLUDE($PHP_CURL_DIR/include) 23 | PHP_ADD_LIBRARY_WITH_PATH(curl, $PHP_CURL_DIR/$PHP_LIB_DIR, TUTORIAL_SHARED_LIBADD) 24 | PHP_SUBST(TUTORIAL_SHARED_LIBADD) 25 | 26 | PHP_NEW_EXTENSION(tutorial, tutorial.c, $ext_shared) 27 | fi 28 | -------------------------------------------------------------------------------- /tutorial.c: -------------------------------------------------------------------------------- 1 | /* tutorial.c */ 2 | 3 | #include "tutorial.h" 4 | #include "zend_exceptions.h" 5 | 6 | #include 7 | 8 | static zend_class_entry *curl_easy_ce = NULL; 9 | static zend_object_handlers curl_easy_handlers; 10 | ZEND_DECLARE_MODULE_GLOBALS(tutorial); 11 | 12 | typedef struct _curl_easy_object { 13 | CURL *handle; 14 | zend_object std; 15 | } curl_easy_object; 16 | 17 | static zend_object* curl_easy_to_zend_object(curl_easy_object *objval) { 18 | return ((zend_object*)(objval + 1)) - 1; 19 | } 20 | static curl_easy_object* curl_easy_from_zend_object(zend_object *objval) { 21 | return ((curl_easy_object*)(objval + 1)) - 1; 22 | } 23 | 24 | static PHP_METHOD(CurlEasy, __construct) { 25 | curl_easy_object *objval = curl_easy_from_zend_object(Z_OBJ_P(getThis())); 26 | char *url = TUTORIALG(default_url); 27 | size_t url_len = 0; 28 | 29 | if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "|p", &url, &url_len) == FAILURE) { 30 | return; 31 | } 32 | 33 | if (url && url[0]) { 34 | CURLcode ret = curl_easy_setopt(objval->handle, CURLOPT_URL, url); 35 | if (ret != CURLE_OK) { 36 | zend_throw_exception(zend_ce_exception, "Failed setting URL", (zend_long)ret); 37 | } 38 | } 39 | } 40 | 41 | static PHP_METHOD(CurlEasy, setOpt) { 42 | curl_easy_object *objval = curl_easy_from_zend_object(Z_OBJ_P(getThis())); 43 | zend_long opt; 44 | zval *value; 45 | CURLcode ret = CURLE_OK; 46 | 47 | if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "lz", &opt, &value) == FAILURE) { 48 | return; 49 | } 50 | 51 | switch (opt) { 52 | case CURLOPT_URL: { 53 | zend_string *strval = zval_get_string(value); 54 | ret = curl_easy_setopt(objval->handle, opt, ZSTR_VAL(strval)); 55 | zend_string_release(strval); 56 | break; 57 | } 58 | case CURLOPT_TIMEOUT: { 59 | zend_long lval = zval_get_long(value); 60 | ret = curl_easy_setopt(objval->handle, opt, lval); 61 | break; 62 | } 63 | default: 64 | zend_throw_exception_ex(zend_ce_exception, opt, "Unknown curl_easy_setopt() option: %ld", (long)opt); 65 | return; 66 | } 67 | if (ret != CURLE_OK) { 68 | zend_throw_exception_ex(zend_ce_exception, ret, "Failed setting option: %ld", (long)opt); 69 | } 70 | RETURN_ZVAL(getThis(), 1, 0); 71 | } 72 | 73 | static PHP_METHOD(CurlEasy, perform) { 74 | if (zend_parse_parameters_none_throw() == FAILURE) { 75 | return; 76 | } 77 | 78 | curl_easy_object *objval = curl_easy_from_zend_object(Z_OBJ_P(getThis())); 79 | curl_easy_perform(objval->handle); 80 | } 81 | 82 | static PHP_METHOD(CurlEasy, escape) { 83 | zend_string *str; 84 | 85 | if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "S", &str) == FAILURE) { 86 | return; 87 | } 88 | 89 | char *escaped = curl_escape(ZSTR_VAL(str), ZSTR_LEN(str)); 90 | if (!escaped) { 91 | zend_throw_exception_ex(zend_ce_exception, 0, "Failed escaping %s", ZSTR_VAL(str)); 92 | return; 93 | } 94 | RETVAL_STRING(escaped); 95 | curl_free(escaped); 96 | } 97 | 98 | static zend_function_entry curl_easy_methods[] = { 99 | PHP_ME(CurlEasy, __construct, NULL, ZEND_ACC_CTOR | ZEND_ACC_PUBLIC) 100 | PHP_ME(CurlEasy, setOpt, NULL, ZEND_ACC_PUBLIC) 101 | PHP_ME(CurlEasy, perform, NULL, ZEND_ACC_PUBLIC) 102 | 103 | PHP_ME(CurlEasy, escape, NULL, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) 104 | PHP_FE_END 105 | }; 106 | 107 | static zend_object* curl_easy_ctor(zend_class_entry *ce) { 108 | curl_easy_object *objval = ecalloc(1, sizeof(curl_easy_object) + zend_object_properties_size(ce)); 109 | objval->handle = curl_easy_init(); 110 | 111 | zend_object* ret = curl_easy_to_zend_object(objval); 112 | zend_object_std_init(ret, ce); 113 | object_properties_init(ret, ce); 114 | ret->handlers = &curl_easy_handlers; 115 | 116 | return ret; 117 | } 118 | 119 | static zend_object* curl_easy_clone(zval *srcval) { 120 | zend_object *zsrc = Z_OBJ_P(srcval); 121 | zend_object *zdst = curl_easy_ctor(zsrc->ce); 122 | zend_objects_clone_members(zdst, zsrc); 123 | 124 | curl_easy_object *src = curl_easy_from_zend_object(zsrc); 125 | curl_easy_object *dst = curl_easy_from_zend_object(zdst); 126 | dst->handle = curl_easy_duphandle(src->handle); 127 | 128 | return zdst; 129 | } 130 | 131 | static void curl_easy_free(zend_object *zobj) { 132 | curl_easy_object *obj = curl_easy_from_zend_object(zobj); 133 | curl_easy_cleanup(obj->handle); 134 | 135 | zend_object_std_dtor(zobj); 136 | } 137 | 138 | PHP_INI_BEGIN() 139 | STD_PHP_INI_ENTRY("tutorial.default_url", "", PHP_INI_ALL, 140 | OnUpdateString, default_url, zend_tutorial_globals, tutorial_globals) 141 | PHP_INI_END() 142 | 143 | static PHP_MINIT_FUNCTION(tutorial) { 144 | zend_class_entry ce; 145 | 146 | if (CURLE_OK != curl_global_init(CURL_GLOBAL_DEFAULT)) { 147 | return FAILURE; 148 | } 149 | 150 | INIT_CLASS_ENTRY(ce, "Tutorial\\CURLEasy", curl_easy_methods); 151 | curl_easy_ce = zend_register_internal_class(&ce); 152 | curl_easy_ce->create_object = curl_easy_ctor; 153 | 154 | memcpy(&curl_easy_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); 155 | curl_easy_handlers.offset = XtOffsetOf(curl_easy_object, std); 156 | curl_easy_handlers.clone_obj = curl_easy_clone; 157 | curl_easy_handlers.free_obj = curl_easy_free; 158 | 159 | zend_declare_class_constant_long(curl_easy_ce, "OPT_URL", strlen("OPT_URL"), CURLOPT_URL); 160 | zend_declare_class_constant_long(curl_easy_ce, "OPT_TIMEOUT", strlen("OPT_TIMEOUT"), CURLOPT_TIMEOUT); 161 | 162 | REGISTER_INI_ENTRIES(); 163 | 164 | return SUCCESS; 165 | } 166 | 167 | static PHP_MSHUTDOWN_FUNCTION(tutorial) { 168 | UNREGISTER_INI_ENTRIES(); 169 | curl_global_cleanup(); 170 | 171 | return SUCCESS; 172 | } 173 | 174 | static PHP_GINIT_FUNCTION(tutorial) { 175 | #if defined(COMPILE_DL_ASTKIT) && defined(ZTS) 176 | ZEND_TSRMLS_CACHE_UPDATE(); 177 | #endif 178 | tutorial_globals->default_url = NULL; 179 | } 180 | 181 | zend_module_entry tutorial_module_entry = { 182 | STANDARD_MODULE_HEADER, 183 | "tutorial", 184 | NULL, /* functions */ 185 | PHP_MINIT(tutorial), 186 | PHP_MSHUTDOWN(tutorial), 187 | NULL, /* RINIT */ 188 | NULL, /* RSHUTDOWN */ 189 | NULL, /* MINFO */ 190 | NO_VERSION_YET, 191 | PHP_MODULE_GLOBALS(tutorial), 192 | PHP_GINIT(tutorial), 193 | NULL, /* GSHUTDOWN */ 194 | NULL, /* RPOSTSHUTDOWN */ 195 | STANDARD_MODULE_PROPERTIES_EX 196 | }; 197 | 198 | #ifdef COMPILE_DL_TUTORIAL 199 | ZEND_GET_MODULE(tutorial) 200 | #endif 201 | 202 | -------------------------------------------------------------------------------- /tutorial.h: -------------------------------------------------------------------------------- 1 | #ifdef HAVE_CONFIG_H 2 | # include "config.h" 3 | #endif 4 | 5 | #include "php.h" 6 | 7 | ZEND_BEGIN_MODULE_GLOBALS(tutorial) 8 | char *default_url; 9 | ZEND_END_MODULE_GLOBALS(tutorial) 10 | 11 | ZEND_EXTERN_MODULE_GLOBALS(tutorial) 12 | 13 | #define TUTORIALG(v) ZEND_MODULE_GLOBALS_ACCESSOR(tutorial, v) 14 | --------------------------------------------------------------------------------