This document provides an overview and introduction to PHP extensions. It discusses compiling PHP with debugging enabled, creating a basic extension skeleton, configuring and installing extensions, and activating extensions. It also covers extension lifetime, PHP memory management using the Zend Memory Manager, PHP variables called zvals which are containers for data, and zval types. The document is intended to provide attendees with the necessary background knowledge to participate in a workshop about PHP extensions.
Introduction to PHP 7 Extensions; speaker background; workshop requirements.
Steps to compile PHP with debug settings for developing extensions.
Creating and activating a PHP extension using skeleton generation and compilation.
Extensions' functionalities include adding new features and performance optimization.
Importance of memory management, memory allocation pools, and usage of ZMM API.
Detailed structure and manipulation methods of zval, a core PHP data type.
Mechanics of references in zvals, manipulation via macros.
Uses and manipulation of strings within PHP extensions.Mechanism to register and handle PHP functions within extensions.
Methods for generating PHP errors and exceptions within extensions.
Registration and management of constants and INI settings within extensions.
Creating and managing classes and objects in PHP extensions and their visibility. Declaring and managing global variables, especially in a threaded context.
Good morning JulienPAULI PHP programmer for many years PHP internals source hacker 5.5 and 5.6 Release Manager Writes tech articles and books http://www.phpinternalsbook.com http://jpauli.github.io Working at SensioLabs in Paris Mainly doing cool C stuff on PHP / Symfony2 @julienpauli - github.com/jpauli - jpauli@php.net
3.
The road CompilePHP and use debug mode PHP extensions details and lifetime PHP extensions globals management Memory management PHP variables : zvals PHP INI settings PHP functions, objects and classes Overwritting existing behaviors Changing deep Zend Engine behaviors
4.
What you shouldbring A laptop under Linux/Unix Good C knowledge Linux knowledge (your-OS knowledge) Any C dev environment Those slides will assume a Debian based Linux
5.
Compiling PHP, usingdebug Grab a PHP source code from php.net or git Install a C code compiling environment You'll probably need some libs to compile PHP :$> apt-get install build-essential autoconf :$> apt-get install libxml2-dev
6.
Compiling PHP, usingdebug Compile a debug PHP and install it Do not forget debug flag Extensions compiled against debug PHP won't load on "normal" PHP => need recompile Always develop extension under debug mode :$> ./configure --enable-debug --prefix=my/install/dir :$> make && make install :$> my/install/dir/bin/php -v PHP 7.0.7-dev (cli) (built: Apr 15 2016 09:11:24) ( NTS DEBUG ) Copyright (c) 1997-2016 The PHP Group Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
7.
Create your firstextension There exists a skeleton generator, let's use it :$> cd phpsrc/ext :$phpsrc/ext> ./ext_skel --extname=extworkshop Creating directory ext-workshop Creating basic files: config.m4 config.w32 .svnignore extworkshop.c php_extworkshop.h CREDITS EXPERIMENTAL tests/001.phpt extworkshop.php [done]. :~> cd extworkshop && tree . |-- config.m4 |-- config.w32 |-- CREDITS |-- EXPERIMENTAL |-- extworkshop.c |-- extworkshop.php |-- php_extworkshop.h `-- tests `-- 001.phpt
8.
Activate your firstextension config.m4 tells the build tools about your ext Uncomment --enable if your extension is stand alone Uncomment --with if your extension has dependencies against other libraries :~/extworkshop> vim config.m4 PHP_ARG_ENABLE(ext-workshop, whether to enable extworkshop support, [ --enable-extworkshop Enable extworkshop support]) PHP_NEW_EXTENSION(extworkshop, extworkshop.c, $ext_shared)
9.
Compile and installyour ext phpize tool is under `php-install-dir`/bin It's a shell script importing PHP sources into your ext dir for it to get ready to compile It performs some checks It imports the configure script This will make you compile a shared object For static compilation, rebuild main configure using buildconf script Run phpize --clean to clean the env when finished :~/extworkshop> phpize && ./configure --with-php-config=/path/to/php-config && make install
10.
API numbers PHPApi Version is the num of the version of the internal API ZendModule API is the API of the extension system ZendExtension API is the API of the zend_extension system ZEND_DEBUG and ZTS are about debug mode activation and thread safety layer activation Those 5 criterias need to match your extension's when you load it Different PHP versions have different API numbers Extensions may not work cross-PHP versions Configuring for: PHP Api Version: 20151012 Zend Module Api No: 20151012 Zend Extension Api No: 320151012
What extensions cando Extensions can : Add new functions, classes, interfaces Add and manage php.ini settings and phpinfo() output Add new global variables or constants Add new stream wrappers/filters, new resource types Overwrite what other extensions defined Hook by overwriting global function pointers Extensions cannot : Modify PHP syntax Zend extensions : Are able to hook into OPArrays (very advanced usage)
14.
Why create anextension ? Bundle an external library code into PHP redis, curl, gd, zip ... so many of them Optimize performances by adding features C is way faster than PHP C is used everywhere in Unix/Linux, including Kernel Create your own C structures and manage them by providing PHP functions Create your own resource intensive algorithms Exemple : https://github.com/phadej/igbinary
15.
C vs PHP Don't try to turn the world to C Why you should use PHP over C : C is way more difficult to develop than PHP C is less maintainable C can be really tricky to debug C is platform dependant. CrossPlatform can turn to PITA Cross-PHP-Version is a pain Why you should use C over PHP : Bundle an external lib into PHP (cant be done in PHP) Looking for very high speed and fast/efficient algos Changing PHP behavior deeply, make it do what you want
Zend Memory ManagerAPI Request-lifetime heap memory should be reclaimed using ZMM API Infinite lifetime memory can be reclaimed using ZMM "persist" API, or direct libc calls #define emalloc(size) #define safe_emalloc(nmemb, size, offset) #define efree(ptr) #define ecalloc(nmemb, size) #define erealloc(ptr, size) #define safe_erealloc(ptr, nmemb, size, offset) #define erealloc_recoverable(ptr, size) #define estrdup(s) #define estrndup(s, length) #define zend_mem_block_size(ptr)
22.
ZMM help ZMMalloc functions track leaks for you They help finding leaks and overwrites If PHP is built with --enable-debug If report_memleaks is On in php.ini (default) Always use ZMM alloc functions Don't hesitate to use valgrind to debug memory USE_ZEND_ALLOC=0 env var disables ZendMM
Zval as acontainer The zval is just a container for your data You provide the data as zend_value Can be anything : double, string, ast, class, function, custom-type You provide some infos about the data (type_info) Is your data requiring heap memory management ? Like strings, like arrays ... What to do when the engine will have to dup() your data ? Can your data be part of a GC cycle ?
Refcounted values Somevalues need to be refcounted a zend_refcounted structure is then used as header typedef struct _zend_refcounted_h { uint32_t refcount; union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, zend_uchar flags, uint16_t gc_info) } v; uint32_t type_info; } u; } zend_refcounted_h;
28.
Zval steps Thereexists tons of macros helping you : Store data into zval Change zval type Change zval real value Deal with zval copy Return zval from PHP functions ...
Zval and pointers You'll manipulate, basically : zval : use MACRO() zval* : use MACRO_P() Read macros expansions Use your IDE Play with pointers Z_ADDREF(myzval) Z_ADDREF_P(myzval *)
Zval Types (LP64) long = 8 bytes double = 8 bytes IEEE754 strings are zend_string structures They are NUL terminated, but may encapsulate NULs They embed their size as a size_t size = number of ASCII chars without ending NUL Many macros to take care of them Bools are stored as a long (1 or 0) Resources are zend_resource Arrays = HashTable type (more later) Objects = lots of things involved (more later)
33.
Zval types differences Complex types need special handling Strings Arrays Objects Resources References Those are refcounted and will embed a zend_refcounted as header Simple types can be carried, copied, destroyed more easilly (long/double/bool/null)
34.
Zval Types macros When you want to read or write a Zval, you use once again dedicated macros : zval myval; ZVAL_DOUBLE(&myval, 16.3); ZVAL_TRUE(&myval); ZVAL_STRINGL(&myval, "foo", sizeof("foo")-1); ZVAL_EMPTY_STRING(&myval); printf("%*s", Z_STRLEN(myval), Z_STRVAL(myval)); printf("%ld", Z_LVAL(myval)); ...
35.
Zval type switching You can ask PHP to switch from a type to another, using its known internal rules Those functions change the zval*, returning void Copy the value, if you need to work on a copy convert_to_array() convert_to_object() convert_to_string() convert_to_boolean() convert_to_null() convert_to_long()
36.
Zval gc info Some values need GC strings, arrays, objects, resources some others don't longs, floats, bools, null, undef Always use ZVAL macros to correctly handle GC refcount, types and heap memory Always think about what's going to happen to your data once thrown into the zend engine ZVAL_COPY_VALUE() Z_TRY_ADDREF() SEPARATE_ZVAL()
37.
Creating and destroyingzvals Creation = simply stack allocation (no heap alloc) Destruction = decrement refcount if data is refcountable, and free it if needed Remember zval is just a container over your data for the engine to carry it zval myval; zval_dtor(&myval);
38.
Copying zvals Youmay want to : Copy a zval content into another without incrementing its refcount Use ZVAL_COPY_VALUE() Copy a zval content into another and increment the GC refcount if needed (very often) Use ZVAL_COPY() Duplicate (deep copy) a zval content into another, and increment GC refcount if needed Use ZVAL_DUP()
39.
Using references Areference may be used as a IS_REFERENCE type A zval is then stored into your zval together with a zend_refcounted header You can create a reference from a zval using ZVAL_NEW_REF() : zval myval, myref; ZVAL_STRING(&myval, "foo"); ZVAL_NEW_REF(&myref, &myval);
40.
Using references Youmay also need to work with a reference Z_REFVAL() to access it through a zval Or unwrap the ref to work with it directly ZVAL_DEREF() Or make a copy of the reference, and work with the copy (separate) ZVAL_DEREF() + ZVAL_DUP()
41.
Strings String usezend_string structure and its API. Many places in the engine will require you to manipulate zend_string zend_string's are refcounted, they may be shared, take care You may like smart_str fast API for string complex constructions (concat) struct _zend_string { zend_refcounted_h gc; zend_ulong h; /* hash value */ size_t len; char val[1]; /* struct hack */ };
Function exercise Declaretwo new functions celsius_to_fahrenheit fahrenheit_to_celsius They should just be empty for the moment Confirm all works
48.
Functions: accepting arguments A very nice API exists Have a look at phpsrc/README.PARAMETER_PARSING_API zend_parse_parameters() converts arguments to the type you ask Follows PHP rules zend_parse_parameters() short : "zpp" zend_parse_parameters(int num_args_to_parse, char* arg_types, (va_arg args...))
49.
Playing with zpp zpp returns FAILURE or SUCCESS On failure, you usually return, the engine takes care of the PHP error message You always use pointers to data in zpp PHP_FUNCTION(foo) { zend_long mylong; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &mylong) == FAILURE) { return; } RETVAL_LONG(mylong); }
50.
zpp formatsa -array (zval*) A - array or object (zval *) b - boolean (zend_bool) C - class (zend_class_entry*) d - double (double) f - function or array containing php method call info (returned as zend_fcall_info and zend_fcall_info_cache) h - array (returned as HashTable*) H - array or HASH_OF(object) (returned as HashTable*) l - long (zend_long) L - long, limits out-of-range numbers to LONG_MAX/LONG_MIN (zend_long) o - object of any type (zval*) O - object of specific type given by class entry (zval*, zend_class_entry) p - valid path (string without null bytes in the middle) and its length (char*, int) P - valid path (string without null bytes in the middle) and its length as zend_string r - resource (zval*) S - string (with possible null bytes) as zend_string s - string (with possible null bytes) and its length (char*, size_t) z - the actual zval (zval*) * - variable arguments list (0 or more) + - variable arguments list (1 or more)
51.
zpp special formats |- indicates that the remaining parameters are optional, they should be initialized to default values by the extension since they will not be touched by the parsing function if they are not passed to it. / - use SEPARATE_ZVAL_IF_NOT_REF() on the parameter it follows ! - the parameter it follows can be of specified type or NULL. If NULL is passed and the output for such type is a pointer, then the output pointer is set to a native NULL pointer. For 'b', 'l' and 'd', an extra argument of type zend_bool* must be passed after the corresponding bool*, long* or double* arguments, respectively. A non-zero value will be written to the zend_bool iif a PHP NULL is passed.
52.
zpp examples char *name,*value = NULL, *path = NULL, *domain = NULL; zend_long expires = 0; zend_bool secure = 0, httponly = 0; size_t name_len, value_len = 0, path_len = 0, domain_len = 0; if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|slssbb", &name, &name_len, &value, &value_len, &expires, &path, &path_len, &domain, &domain_len, &secure, &httponly) == FAILURE) { return; } /* Gets an object or null, and an array. If null is passed for object, obj will be set to NULL. */ zval *obj; zval *arr; if (zend_parse_parameters(ZEND_NUM_ARGS(), "o!a", &obj, &arr) == FAILURE) { return; }
53.
Practice zpp makeour temperature functions accept argument and return a true result Parse the argument Check RETVAL_**() macros, they'll help °C x 9/5 + 32 = °F (°F - 32) x 5/9 = °C
54.
Writing a test PHP's got a framework for testing itself and its extensions Welcome "PHPT" Learn more about it at http://qa.php.net/write-test.php
Generating errors Twokinds : Errors Exceptions For errors : php_error_docref() : Sends an error with a docref php_error() / zend_error() : Sends an error For exceptions : zend_throw_exception() : throws an exception php_error(E_WARNING, "The number %lu is too big", myulong); zend_throw_exception_ex(zend_exception_get_default(), 0, "%lu too big", myulong);
58.
Practice errors Createa function temperature_converter($value, $convert_type) convert_type can only be 1 or 2 1 = F° to C° 2 = C° to F° It should output an error if $convert_type is wrong The function should return a string describing the scenario run Have a look at php_printf() function to help echo temperature_converter(20, 2); "20 degrees celsius give 68 degrees fahrenheit" echo temperature_converter(20, 8); Warning: convert_type not recognized
59.
A quick wordon string formats Know your libc's printf() formats http://www.cplusplus.com/reference/cstdio/printf/ Always use right formats with well sized buffers Lot's of PHP functions use "extra", internal implementation of libc's printf()/spprintf()/snprintf() Error messages for example Read spprintf.c and snprintf.h to know more about PHP specific formats, such as "%Z" Lots of nice comments in those sources
60.
Function argument declaration zpp is clever enough to compute needed args zpp uses ZEND_NUM_ARGS() it may return FAILURE if number is incorrect PHP_FUNCTION(foo) { long mylong; if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &mylong) == FAILURE) { return; } } <?php foo(); Warning: foo() expects exactly 1 parameter, 0 given in /tmp/myext.php on line 3
61.
Function args declaration Try to use Reflection on your temperature functions $> php -dextension=extworkshop.so --rf temperature_converter
62.
Function args declaration You may help reflection knowing about accepted parameters For this, you need to declare them all to the engine The engine can't compute them by itself Welcome "arginfos"
HashTable quickly Cnoticeable structure Lots of ways to implement them in C Mostly lots of operations are O(1) with worst case O(n) http://lxr.linux.no/linux+v3.12.5/include/linux/list.h#L560 Used everywhere, in every strong program Implementation of PHP arrays Keys can be numeric type or string type Values can be any type
Zend HashTables zend_hash.c/ zend_hash.h HashTable struct big API Doubled : weither key is numeric or string Only stores zvals, nothing else HashTables are widely used into PHP Not only PHP arrays, they are used internally everywhere gdb functions in .gdbinit to help debugging them
Zend HashTable API Hash size is rounded up to the next power of two If size is exceeded, HashTable will automatically be resized, but at a (low) CPU cost pHashFunction is not used anymore, give NULL pDestructor is the destructor function Will be called on each data stored in each zval- element when you remove it from the Hash This is used to manage memory : usually free it If your zvals need custom storage, use a destructor int zend_hash_init(HashTable *ht, uint nSize, hash_func_t pHashFunction, dtor_func_t pDestructor, zend_bool persistent);
Zend HT commonmistakes A HashTable is not a zval PHP_FUNCTIONs() mainly manipulate zvals (return_value) Use, f.e. array_init()/ZVAL_NEW_ARR to create a zval containing a HT Access the HT into a zval using Z_ARR() macro types Lots of HashTable functions return a zval* on success or NULL HashTables manipulate only zval* HashTables manipulate zend_string as string-based keys char * / size_t couple is also possible
74.
HashTable retrieve API PHP_FUNCTION(foo) { HashTable*myht; zval *data = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS(), "h", &myht) == FAILURE) { return; } if ((data = zend_hash_str_find(myht, "foo", strlen("foo")) == NULL) { php_error(E_NOTICE, "Key 'foo' does not exist"); return; } RETVAL_ZVAL(data, 1, 0); }
75.
HashTable exercise Createa function that accepts an infinity of temperature values into an array and converts them back to C or F --TEST-- Test temperature converter array <?php $temps = array(68, 77, 78.8); var_dump(multiple_fahrenheit_to_celsius($temps)); ?> --EXPECTF-- array(3) { [0]=> float(20) [1]=> float(25) [2]=> float(26) }
76.
References exercise Turnmultiple_fahrenheit_to_celsius() into an accept-by-reference function --TEST-- Test temperature converter array by-ref <?php $temps = array(68, 77, 78.8); multiple_fahrenheit_to_celsius($temps)); var_dump($temps); ?> --EXPECTF-- array(3) { [0]=> float(20) [1]=> float(25) [2]=> float(26) }
77.
Constants Constants arereally easy to use into the engine You usually register yours in MINIT() phase, use CONST_PERSISTENT (if not, const will be cleared at RSHUTDOWN) You can read any constant with an easy API PHP_MINIT_FUNCTION(extworkshop) { REGISTER_STRING_CONSTANT("fooconst", "foovalue", CONST_CS | CONST_PERSISTENT); return SUCCESS; } zend_module_entry myext_module_entry = { STANDARD_MODULE_HEADER, "extworkshop", extworkshop_functions, /* Function entries */ PHP_MINIT(extworkshop), /* Module init */ NULL, /* Module shutdown */ ...
INI general concepts Each extension may register as many INI settings as it wants Remember INI entries may change during request lifetime They store both their original value and their modified (if any) value They store an access level to declare how their value can be altered (PHP_INI_USER, PHP_INI_SYSTEM, etc...) PHP's ini_set() modifies the entry value at runtime PHP's ini_restore() restores the original value as current value INI entries may be displayed (mainly using phpinfo()), they embed a "displayer" function pointer INI entries are attached to an extension
84.
An INI entryin PHP struct _zend_ini_entry_def { const char *name; ZEND_INI_MH((*on_modify)); void *mh_arg1; void *mh_arg2; void *mh_arg3; const char *value; void (*displayer)(zend_ini_entry *ini_entry, int type); int modifiable; uint name_length; uint value_length; };
85.
INI entries main Register at MINIT Unregister at MSHUTDOWN Display in phpinfo() (usually) Many MACROS (once more) Read the original or the modified value (your choice) in your extension Create your own modifier/displayer (if needed)
86.
My first INIentry This declares a zend_ini_entry vector Register / Unregister it Display it in phpinfo PHP_INI_BEGIN() PHP_INI_ENTRY("logger.default_file", LOGGER_DEFAULT_LOG_FILE, PHP_INI_ALL, NULL) PHP_INI_END() #define PHP_INI_ENTRY(name, default_value, modifiable, on_modify) PHP_MINIT_FUNCTION(myext) { REGISTER_INI_ENTRIES(); ... ... } PHP_MSHUTDOWN_FUNCTION(myext) { UNREGISTER_INI_ENTRIES(); ... ... } PHP_MINFO_FUNCTION(myext) { DISPLAY_INI_ENTRIES(); ... ... }
87.
Using an INIentry To read your entry, use one of the MACROs Same way to read the original value : INI_STR(entry); INI_FLT(entry); INI_INT(entry); INIT_BOOL(entry); INI_ORIG_STR(entry); INI_ORIG_FLT(entry); INI_ORIG_INT(entry); INIT_ORIG_BOOL(entry);
88.
Modifying an INIentry INI entries may be attached a "modifier" A function pointer used to check the new attached value and to validate it For example, for bools, users may only provide 1 or 0, nothing else Many modifiers/validators already exist : You may create your own modifier/validator OnUpdateBool OnUpdateLong OnUpdateLongGEZero OnUpdateReal OnUpdateString OnUpdateStringUnempty
89.
Using a modifier The modifier should return FAILURE or SUCCESS The engine takes care of everything Access control, error message, writing to the entry... PHP_INI_BEGIN() PHP_INI_ENTRY("logger.default_file", LOGGER_DEFAULT_LOG_FILE, PHP_INI_ALL, OnUpdateStringUnempty) PHP_INI_END() ZEND_API ZEND_INI_MH(OnUpdateStringUnempty) { char **p; char *base = (char *) mh_arg2; if (new_value && !ZSTR_VAL(new_value)[0]) { return FAILURE; } p = (char **) (base+(size_t) mh_arg1); *p = new_value ? ZSTR_VAL(new_value) : NULL; return SUCCESS; }
90.
Linking INI entryto a global If you use your entry often by accessing it, you will trigger a hash lookup everytime This is not nice for performance Why not have a global of yours change when the INI entry is changed (by the PHP user likely)? Please, welcome "modifiers linkers"
91.
Linking INI entryto a global Declare a global struct, and tell the engine which field it must update when your INI entry gets updated typedef struct myglobals { char *my_path; void *some_foo; void *some_bar; } myglobals; static myglobals my_globals; /* should be thread protected */ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("logger.default_file", LOGGER_DEFAULT_LOG_FILE, PHP_INI_ALL, OnUpdateStringUnempty, my_path, myglobals, my_globals) PHP_INI_END()
Classes and objects More complex than functions as more structures are involved zend_class_entry Represents a class zend_object Represents an object zend_object_handlers Function pointers to specific object actions (lots of them) zend_object_store Big global single object repository storing every known object
94.
All starts witha class A very big structure : zend_class_entry Lots of macros to help managing classes Internal classes need to be registered at MINIT() Internal classes are not destroyed at the end of the request (user classes are) An interface is a (special) class A trait is a (special) class Once a class is registered into the engine, you may create as many objects as you want with low memory footprint
95.
Registering a newclass zend_register_internal_class() Takes a zend_class_entry* as model Initialize internal class members Registers the class into the engine Returns a new pointer to this freshly added class zend_class_entry *ce_Logger; PHP_MINIT_FUNCTION(myext) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Logger", NULL); ce_Logger = zend_register_internal_class(&ce); return SUCCESS; }
96.
Registering a newclass Usually the class pointer is shared into a global variable This one should be exported in a header file This allows other extensions to use/redefine our class zend_class_entry *ce_Logger; PHP_MINIT_FUNCTION(myext) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Logger", NULL); ce_Logger = zend_register_internal_class(&ce); return SUCCESS; }
97.
Other class noticeableitems zend_class_entry also manages Static attributes (zvals) Constants (zvals) Functions (object methods and class static methods) Interfaces (zend_class_entry as well) Inheritence classes tree Used traits (zend_class_entry again) Other stuff such as handlers We'll see how to take care of such item later on
98.
An example loggerclass We will design something like that : Now : register the Logger class <?php try { $log = new Logger('/tmp/mylog.log'); } catch (LoggerException $e) { printf("Woops, could not create object : %s", $e->getMessage()); } $log->log(Logger::DEBUG, "My debug message");
class/object attributes zend_declare_property_<type>() Can declare both static and non static attr. Can declare any visibility, only type matters PHP_MINIT_FUNCTION(myext) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Logger", NULL); ce_Logger = zend_register_internal_class(&ce); zend_declare_property_string(ce_Logger, "file", strlen("file"), "", ZEND_ACC_PROTECTED); }
101.
Practice creating aclass Create the logger class With 3 constants : INFO, DEBUG, ERROR With 2 properties handle : private , null file : protected , string You may declare a namespaced class Use INIT_NS_CLASS_ENTRY for this
102.
Adding methods Methodsare just functions attached to a class Very common with PHP_FUNCTION ZEND_BEGIN_ARG_INFO(arginfo_logger___construct, 0) ZEND_ARG_INFO(0, value) ZEND_END_ARG_INFO() static zend_function_entry logger_class_functions[] = { PHP_ME( Logger, __construct, arginfo_logger___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR ) PHP_FE_END }; PHP_METHOD( Logger, __construct ) { /* some code here */ } PHP_MINIT_FUNCTION(myext) { zend_class_entry ce; INIT_CLASS_ENTRY(ce, "Logger", logger_class_functions); /* ... */ }
103.
Visibility modifier Onemay use ZEND_ACC_PROTECTED ZEND_ACC_PUBLIC ZEND_ACC_PRIVATE ZEND_ACC_FINAL ZEND_ACC_ABSTRACT ZEND_ACC_STATIC Usually, the other flags (like ZEND_ACC_INTERFACE) are set by the engine when you call proper functions ZEND_ACC_CTOR/DTOR/CLONE are used by reflection only
Designing and usinginterfaces An interface is a zend_class_entry with special flags and abstract methods only zend_register_internal_interface() is used It simply sets ZEND_ACC_INTERFACE on the zend_class_entry structure zend_class_implements() is then used to implement the interface
106.
Practice : addan interface Detach the log() method into an interface and implement it
107.
Exceptions Use zend_throw_exception()to throw an Exception Passing NULL as class_entry will use default Exception You may want to register and use your own Exceptions Just create your exception class Make it extend a base Exception class Use zend_register_class_entry_ex() for that zend_throw_exception(my_exception_ce, "An error occured", 0); INIT_CLASS_ENTRY(ce_exception, "LoggerException", NULL); ce_Logger_ex = zend_register_internal_class_ex(&ce_exception, zend_exception_get_default(), NULL);
108.
Practice Write realcode for our Logger class You may need to update properties zend_update_property_<type>() You may need to access $this in your methods use getThis() macro or this_ptr from func args You may need to use php streams If so, try getting used to their API by yourself
Globals ? Theyare sometimes (often) needed Every program needs some kind of global state Try however to prevent their usage when possible Use reentrancy instead
111.
PHP globals problem If the environnement is threaded, many (every?) global access in write should be protected Basicaly using some kind of locking technology PHP can be compiled with ZendThreadSafety (ZTS) or not (NZTS) Globals access in ZTS mode need to be mutexed Globals access in NZTS mode don't need such protection So accessing globals will differ according to ZTS or not Thus we'll use macros for such tasks
112.
Declaring globals Declarethe structure (usually in .h) Declare a variable holding the structure Declare TSRM STATIC CACHE ZEND_BEGIN_MODULE_GLOBALS(extworkshop) char *my_string; ZEND_END_MODULE_GLOBALS(extworkshop) ZEND_DECLARE_MODULE_GLOBALS(extworkshop) ZEND_TSRMLS_CACHE_DEFINE()
113.
Initializing globals Mostlikely, your globals will need to be initialized (most likely, to 0). There exists two hooks for that Right before MINIT : GINIT Right after MSHUTDOWN : GSHUTDOWN Remember that globals are global (...) Nothing to do with request lifetime Booted very early Destroyed very late
Accessing globals Amacro is present in your .h for that YOUR-EXTENSION-NAME_G(global_value_to_fetch) #ifdef ZTS #define EXTWORKSHOP_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(extworkshop, v) #else #define EXTWORKSHOP_G(v) (extworkshop_globals.v) #endif if (EXTWORKSHOP_G(my_string)) { ... }
117.
Globals : practice Move our resource_id to a protected global Our extension is now Thread Safe !