Обработчики объектов

Почти все операции над объектами в PHP проходят через обработчики объектов, и каждый магический метод или магический интерфейс внутренне реализован через обработчик объекта или класса. Более того, есть довольно много обработчиков, которые не экспонируются в пользовательский код PHP. Например, внутренние классы могут иметь собственное поведение сравнения и приведения типов.

Обзор

Вот все обработчики объектов с их сигнатурами и небольшим описанием.

zval *read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv)
zval *write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot)
int has_property(zend_object *zobj, zend_string *name, int has_set_exists, void **cache_slot)
void unset_property(zend_object *zobj, zend_string *name, void **cache_slot)
zval *get_property_ptr_ptr(zend_object *zobj, zend_string *name, int type, void **cache_slot)

Эти обработчики соответствуют методам __get, __set, __isset и __unset. get_property_ptr_ptr — это внутренний эквивалент __get, возвращающий значение по ссылке. cache_slot используется для хранения смещения свойства и zend_property_info. read_property может напрямую вернуть zval, принадлежащий объекту, в этом случае его счётчик ссылок не должен изменяться read_property, и вызывающая сторона не должна его освобождать. В качестве альтернативы, он может вернуть rv для временных zval (например, результат вызова __get), в этом случае счётчик ссылок должен быть увеличен, и ответственность за освобождение значения лежит на вызывающей стороне.

zval *read_dimension(zend_object *object, zval *offset, int type, zval *rv)
void write_dimension(zend_object *object, zval *offset, zval *value)
int has_dimension(zend_object *object, zval *offset, int check_empty)
void unset_dimension(zend_object *object, zval *offset)

Этот набор обработчиков является внутренним представлением интерфейса ArrayAccess. zval *rv в read_dimension используется для временных значений, возвращаемых из offsetGet и offsetExists.

HashTable *get_properties(zend_object *zobj)
HashTable *get_debug_info(zend_object *object, int *is_temp)

Используются для получения свойств объекта в виде хеш-таблицы. Первый более общего назначения, например, он также используется для функции get_object_vars. Второй же, напротив, используется исключительно для отображения свойств в функциях отладки, таких как var_dump. Поэтому, даже если ваш объект не предоставляет никаких формальных свойств, вы всё равно можете иметь содержательный вывод для отладки.

zend_function *get_method(zend_object **obj_ptr, zend_string *method_name, const zval *key)

Обработчик get_method получает zend_function, используемую для вызова определённого метода. Опционально key может быть передан в качестве оптимизации, чтобы избежать перевода method_name в нижний регистр, если он уже присутствует.

zend_function *get_constructor(zend_object *zobj)

Аналогично get_method, но получает функцию-конструктор. Самая распространённая причина переопределить этот обработчик — запретить ручное конструирование, выбросив ошибку в обработчике.

zend_result count_elements(zend_object *object, zend_long *count)

Это просто внутренний способ реализации метода Countable::count. Функция возвращает zend_result и присваивает значение указателю zend_long *count.

int compare(zval *o1, zval *o2)

Обработчик compare — это обязательный обработчик, который вычисляет равенство данного объекта и другого значения. Обратите внимание, что другое значение не обязательно является объектом того же класса, или даже объектом вообще. Обработчик должен возвращать отрицательные числа, если левый операнд меньше, 0, если они равны, или положительное число, если левый операнд больше. Если значения несравнимы, должно быть возвращено ZEND_UNCOMPARABLE.

zend_result cast_object(zend_object *readobj, zval *writeobj, int type)

Внутренние классы имеют возможность реализовывать собственное поведение сравнения и переопределять поведение приведения типов для всех типов. Пользовательские классы, напротив, могут переопределить только приведение объекта к строке через __toString.

zend_result get_closure(zend_object *obj, zend_class_entry **ce_ptr, zend_function **fptr_ptr, zend_object **obj_ptr, bool check_only)

Этот обработчик вызывается, когда объект используется как функция, то есть это внутренняя версия __invoke. Название происходит от того, что его основное применение — реализация замыканий (класс Closure).

zend_string *get_class_name(const zend_object *zobj)

Этот обработчик используется для получения имени класса из объекта в контекстах отладки. Переопределять его практически нет причин.

zend_object *clone_obj(zend_object *old_object)

Обработчик clone_obj вызывается при выполнении clone $old_object. По умолчанию PHP выполняет поверхностное клонирование объектов, что означает, что свойства, содержащие объекты, не клонируются, но и старый, и новый объект будут указывать на один и тот же объект. clone_obj позволяет настроить это поведение. Он также используется для полного запрета clone.

HashTable *get_gc(zend_object *zobj, zval **table, int *n)

Обработчик get_gc должен возвращать все переменные, которые удерживает объект, чтобы циклические зависимости могли быть корректно собраны сборщиком мусора. Если объект не поддерживает хеш-карту свойств (потому что он не хранит никаких динамических свойств), он может использовать table для хранения указателя непосредственно на список zval, вместе со счётчиком свойств.

void dtor_obj(zend_object *object)
void free_obj(zend_object *object)

dtor_obj вызывается перед free_obj. Объект должен оставаться в допустимом состоянии после завершения работы dtor_obj. В отличие от free_obj, он выполняется до деактивации исполнителя во время завершения работы, что позволяет выполнять пользовательский код. Не гарантируется, что этот обработчик будет вызван (например, при фатальной ошибке), и поэтому его не следует использовать для освобождения ресурсов или памяти. Более того, освобождение ресурсов в этом обработчике может нарушить обнаружение утечек памяти, так как циклы могут быть разорваны заранее. dtor_obj следует использовать только для вызова пользовательских хуков уничтожения, таких как __destruct.

free_obj должен освобождать любые ресурсы, удерживаемые объектом, не освобождая саму структуру объекта. Объекту не обязательно оставаться в допустимом состоянии после завершения работы free_obj. free_obj будет вызван всегда, даже если объект “утекает” или происходит фатальная ошибка. Однако во время завершения работы он может быть вызван после того, как исполнитель уже не активен, и в этом случае выполнение пользовательского кода может быть пропущено.

zend_result do_operation(zend_uchar opcode, zval *result, zval *op1, zval *op2)

do_operation — это опциональный обработчик, который будет вызван для различных арифметических и бинарных операций на экземплярах данного класса. Это позволяет реализовать семантику перегрузки операторов для пользовательских классов. Примеры перегружаемых операторов: +, -, *, /, ++, --, !.

zend_array *get_properties_for(zend_object *object, zend_prop_purpose purpose)

get_properties_for может быть использован для настройки списка свойств объекта, возвращаемого для различных целей. Цели определены в zend_prop_purpose, который на текущий момент включает print_r, var_dump, приведение к (array), serialize, var_export и json_encode.