2. Good morning
Julien PAULI
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
Compile PHP 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 should bring
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, using debug
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, using debug
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 first extension
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 first extension
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 install your 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
PHP Api 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
13. What extensions can do
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 an extension ?
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
21. Zend Memory Manager API
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
ZMM alloc 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
25. Zval as a container
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 ?
27. Refcounted values
Some values 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
There exists 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
...
29. Zval main macros
Play with refcount/is_ref
ZVAL_MAKE_REF()
Z_REFCOUNT()
Z_ADDREF() / Z_TRY_ADDREF()
Z_DELREF() / Z_TRY_DELREF()
Z_ISREF()
Z_SET_REFCOUNT()
Copy / separate
ZVAL_COPY_VALUE()
ZVAL_COPY()
ZVAL_DUP()
zval_copy_ctor()
SEPARATE_ZVAL()
SEPARATE_ZVAL_IF_NOT_REF()
Type jugling
Z_TYPE()
Z_TYPE_INFO()
Z_TYPE_FLAGS()
ZVAL_STRING()
ZVAL_STRINGL()
ZVAL_EMPTY_STRING()
ZVAL_TRUE()
ZVAL_FALSE()
ZVAL_LONG()
ZVAL_ARR()
ZVAL_DOUBLE()
ZVAL_RESOURCE()
30. 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 *)
32. 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 destroying zvals
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
You may 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
A reference 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
You may 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 use zend_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 */
};
47. Function exercise
Declare two 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
make our 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
57. Generating errors
Two kinds :
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
Create a 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 word on 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"
67. HashTable quickly
C noticeable 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
69. 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
71. 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);
72. Zend HashTable API example
HashTable myht = {0};
zend_hash_init(&myht, 10, NULL, NULL, 0);
zval myval;
ZVAL_STRINGL(myval, "Hello World", strlen("Hello World"));
if (zend_hash_str_add(&myht, "myvalue", strlen("myvalue"), &myval) == NULL) {
php_error(E_WARNING, "Could not add value to Hash");
} else {
php_printf("The hashTable contains %lu elements", zend_hash_num_elements(&myht));
}
73. Zend HT common mistakes
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
Create a 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
Turn multiple_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 are really 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 */
...
83. 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 entry in 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 INI entry
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 INI entry
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 INI entry
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 entry to 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 entry to 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()
93. 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 with a 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 new class
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 new class
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 noticeable items
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 logger class
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");
99. class constants
zend_declare_class_constant_<type>()
Will use ce->constants_table HashTable
#define LOG_INFO 2
PHP_MINIT_FUNCTION(myext)
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Logger", NULL);
ce_Logger = zend_register_internal_class(&ce);
zend_declare_class_constant_long(ce_Logger, "INFO", strlen("INFO"), LOG_INFO);
}
100. 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 a class
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
Methods are 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
One may 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
105. Designing and using interfaces
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 : add an 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 real code 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
110. Globals ?
They are 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
Declare the 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
Most likely, 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
116. Accessing globals
A macro 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 !