diff --git a/README.md b/README.md index 42b3fcd..250a3f1 100644 --- a/README.md +++ b/README.md @@ -52,9 +52,9 @@ opcache.enable_cli=1 ffi.enable="true" ; List of headers files to preload, wildcard patterns allowed. `ffi.preload` has no effect on Windows. -ffi.preload=path/to/vendor/symplely/zend-ffi/headers/ze{%php version%}_vendor.h +;ffi.preload=path/to/vendor/symplely/zend-ffi/headers/ze(your-php-version)_vendor.h -opcache.preload==path/to/vendor/symplely/zend-ffi/preload.php +;opcache.preload==path/to/vendor/symplely/zend-ffi/preload.php ``` For a simple FFI integration process **create/edit**: diff --git a/tests/015-stack_resources_fd.phpt b/tests/015-stack_resources_fd.phpt index 6655a00..c142282 100644 --- a/tests/015-stack_resources_fd.phpt +++ b/tests/015-stack_resources_fd.phpt @@ -10,7 +10,7 @@ $fd = fopen(__FILE__, 'r'); var_dump($fd); $int_fd = \get_fd_resource($fd); var_dump($int_fd); -var_dump(create_resource_fd($int_fd)); +var_dump(create_resource_fd($int_fd, new \stdClass)); [$zval, $fd1] = \zval_to_fd_pair($fd); var_dump($zval, $fd1); var_dump(\get_resource_fd($fd1)); diff --git a/zend/Functions.php b/zend/Functions.php index ace1815..205a5e3 100644 --- a/zend/Functions.php +++ b/zend/Functions.php @@ -238,12 +238,14 @@ function zval_is_dtor(object $instance): bool /** * Returns an _instance_ that's a cross platform representation of a file handle. * - * @param string $type platform file `typedef` handle. + * @param string $type platform _file descriptor_ handle to create, example: `php_socket_t`. + * @param bool $create `false` to stop `cdata` creation. + * - Use to return an class instance with no `cdata` object. * @return Resource */ - function fd_type(string $type = 'uv_file'): Resource + function fd_type(string $type = 'uv_file', bool $create = true): Resource { - return Resource::init($type); + return Resource::init($type, $create); } /** @@ -372,15 +374,50 @@ function create_resource(CData $fd_ptr, string $type = 'stream', int $module = \ } /** - * Create `resource` from `int`, _bind_ to an **CData** `object`. + * Same as `get_resource_fd()`, but holds reference to an `object`. + * - This returns the static held reference in ZE `Resource` class, or creates one. * * @param integer $fd - * @param object $extra + * @param object $store * @return resource */ - function create_resource_fd(int $fd, object $extra = null) + function create_resource_fd(int $fd, object $store = null) { - return PhpStream::fd_to_zval($fd, 'wb+', false, $extra); + return PhpStream::fd_to_zval($fd, 'wb+', false, $store); + } + + /** + * Create and register `resource` from **CData** `object`, _bind_ to `int` **fd**. + * - by way of `zend_register_resource()`. + * - This will also create static held reference in ZE `Resource` class. + * + * @param integer $fd + * @param object $cdata PHP `__invoke()` instance that returns `CData` + * @param string $type resource string type + * @param callable $rsrc + * @return resource + */ + function create_resource_object(int $fd, object $cdata, string $type = 'stream', callable $rsrc = null) + { + $object_ptr = $cdata(); + $object_res = \zend_register_resource( + $object_ptr, + \zend_register_list_destructors_ex((\is_null($rsrc) + ? function (CData $rsrc) { + } : $rsrc), + null, + $type, + \ZEND_MODULE_API_NO + ) + ); + + $object_zval = \zval_resource($object_res); + $resource = \zval_native($object_zval); + $file = \fd_type('', false); + $file->add_object($cdata); + $file->add_fd_pair($fd, $resource); + + return $resource; } function zend_reference(&$argument): ZendReference @@ -447,34 +484,37 @@ function zval_blank(): Zval } /** + * - This returns the static held reference in ZE `Resource` class, or creates one. * @param resource $stream * @return array */ - function zval_to_fd_pair($stream): array + function zval_to_fd_pair($stream, string $typedef = 'php_socket_t'): array { $zval = \zval_constructor($stream); - $fd = PhpStream::zval_to_fd($zval); + $fd = PhpStream::zval_to_fd($zval, $typedef); return [$zval, $fd]; } /** * Return `int` of _file descriptor_ from a **resource**, after converting into/from `php_stream` C struct. + * - This returns the static held reference in ZE `Resource` class, or creates one. * * @param resource|int $fd * @return int|uv_file `fd` */ - function get_fd_resource($fd): int + function get_fd_resource($fd, string $typedef = 'php_socket_t'): int { if (!\is_resource($fd) && !\is_integer($fd)) return \ze_ffi()->zend_error(\E_WARNING, "only resource or int types allowed"); - return PhpStream::zval_to_fd(\zval_constructor($fd)); + return PhpStream::zval_to_fd(\zval_constructor($fd), $typedef); } /** * Remove/free any `resource` created with `get_resource_fd()`, `get_fd_resource()`, * `get_socket_fd()`, or `zval_to_fd_pair()`. + * - This removes the static held reference from ZE `Resource` class. * * @param resource|int|Zval $fd_Int_Zval * @return void @@ -495,8 +535,26 @@ function remove_fd_resource(...$fd_Int_Zval): void } } + /** + * Returns any `resource` created with `get_resource_fd()`, `get_fd_resource()`, `get_socket_fd()`, + * `create_resource_object()`, `create_resource_fd()` or `zval_to_fd_pair()`. + * - This returns the static held reference in ZE `Resource` class. + * + * @param integer $handle + * @param boolean $get_int + * @param boolean $get_pair + * @param boolean $get_instance + * + * @return Resource|int|array|CData|null + */ + function resource_get_fd(int $handle, bool $get_int = true, bool $get_pair = false, bool $get_instance = false) + { + return Resource::get_fd($handle, $get_int, $get_pair, $get_instance); + } + /** * Return `resource` from `int` a _file descriptor_, after converting into/from `php_stream` C struct. + * - This returns the static held reference in ZE `Resource` class, or creates one. * * @param int $fd * @param string $mode @@ -510,6 +568,7 @@ function get_resource_fd($fd, string $mode = 'wb+', bool $getZval = false) /** * Return `int` of _file descriptor_ from a **socket**, after converting into/from `php_socket` C struct. + * - This returns the static held reference in ZE `Resource` class, or creates one. * * @param resource|int|\Socket $fd * @return php_socket_t|int diff --git a/zend/StandardModule.php b/zend/StandardModule.php index 638eb74..5e91298 100644 --- a/zend/StandardModule.php +++ b/zend/StandardModule.php @@ -215,17 +215,12 @@ final public function module_destructor(): void $this->global_rsrc = null; } - $this->ze_other_ptr = null; - $this->ze_other = null; - $this->reflection = null; if (!$this->module_destructor_linked) - static::set_module(null); - - \zend_hash_delete($this->module_name); + static::clear_module(); } } - final protected static function set_module(?\StandardModule $module): void + final protected static function set_module(\StandardModule $module): void { if (\PHP_ZTS) self::$global_module[\ze_ffi()->tsrm_thread_id()] = $module; @@ -233,6 +228,33 @@ final protected static function set_module(?\StandardModule $module): void self::$global_module[static::class] = $module; } + /** + * Force clear and shutdown module. + * - Will cause `PHP_MSHUTDOWN_FUNCTION()` and `PHP_GSHUTDOWN_FUNCTION()` to execute. + * + * @return void + */ + final public static function clear_module(): void + { + /** @var static */ + $module = (\PHP_ZTS) + ? self::$global_module[\ze_ffi()->tsrm_thread_id()] + : self::$global_module[static::class]; + + if (!\is_null($module)) { + if (\PHP_ZTS) + self::$global_module[\ze_ffi()->tsrm_thread_id()] = null; + else + self::$global_module[static::class] = null; + + \zend_hash_delete($module->module_name); + $module->ze_other_ptr = null; + $module->ze_other = null; + $module->reflection = null; + \zval_del_ref($module); + } + } + /** * Represents `ZEND_GET_MODULE()` _macro_. * diff --git a/zend/Types/PhpStream.php b/zend/Types/PhpStream.php index 172500f..52245cf 100644 --- a/zend/Types/PhpStream.php +++ b/zend/Types/PhpStream.php @@ -12,9 +12,9 @@ if (!\class_exists('PhpStream')) { final class PhpStream extends Resource { - public static function init(string $type = null): self + public static function init(string $type = 'php_socket_t', bool $create = true): self { - return new static('struct _php_stream', false); + return new static('struct _php_stream', $create); } /** @@ -87,14 +87,14 @@ public static function open_wrapper( * @param bool $getZval * @return resource|Zval|null */ - public static function fd_to_zval($fd, $mode = 'wb+', bool $getZval = false, object $extra = null) + public static function fd_to_zval($fd, $mode = 'wb+', bool $getZval = false, object $store = null) { - $stream = Resource::get_fd($fd, true); - if ($stream instanceof Zval) { + $stream = \resource_get_fd($fd, false, true); + if (\is_array($stream)) { if ($getZval) - return $stream; + return \zval_constructor($stream[1]); - return \zval_native($stream); + return $stream[1]; } $stream = \ze_ffi()->_php_stream_fopen_from_fd($fd, $mode, null); @@ -102,12 +102,12 @@ public static function fd_to_zval($fd, $mode = 'wb+', bool $getZval = false, obj try { $zval = PhpStream::init_stream($stream); $resource = \zval_native($zval); - $php_stream = \fd_type(); - $php_stream->update(\ffi_null(), true); - if (!\is_null($extra)) - $php_stream->add_object($extra); + $php_stream = \fd_type('', false); + $php_stream->update($stream, true); + if (!\is_null($store)) + $php_stream->add_object($store); - $php_stream->add_pair($zval, $fd, (int)$resource); + $php_stream->add_fd_pair($fd, $resource); } catch (\Throwable $e) { return \ze_ffi()->_php_stream_free($stream, self::PHP_STREAM_FREE_CLOSE); } @@ -142,9 +142,9 @@ public static function php_stream_from_res(ZendResource $res): ?PhpStream return !\is_cdata($stream) ? null : static::init_value($stream); } - protected static function to_descriptor(Zval $ptr) + protected static function to_descriptor(Zval $ptr, string $type = 'php_socket_t') { - $zval_fd = \fd_type(); + $zval_fd = \fd_type($type); $fd = $zval_fd(); $stream = \ze_ffi()->cast( 'php_stream*', @@ -177,17 +177,17 @@ protected static function to_descriptor(Zval $ptr) * @param Zval $ptr * @return int|uv_file `fd` */ - public static function zval_to_fd(Zval $ptr): int + public static function zval_to_fd(Zval $ptr, string $typedef = 'php_socket_t'): int { $fd = -1; $type = $ptr->macro(\ZE::TYPE_P); if ($type === \ZE::IS_RESOURCE) { $handle = $ptr()->value->res->handle; - $zval = Resource::get_fd($handle, true); - if ($zval instanceof Zval) - return Resource::get_fd($handle, false, false, true); + $fd = \resource_get_fd($handle); + if (!\is_null($fd)) + return $fd; - $zval_fd = static::to_descriptor($ptr); + $zval_fd = static::to_descriptor($ptr, $typedef); $fd = \is_null($zval_fd) ? -1 : $zval_fd(); } elseif ($type === \ZE::IS_LONG) { $fd = $ptr->macro(\ZE::LVAL_P); @@ -199,7 +199,7 @@ public static function zval_to_fd(Zval $ptr): int if (\is_cdata($fd)) { $fd = $fd[0]; - $zval_fd->add_pair($ptr, $fd, $handle); + $zval_fd->add_fd_pair($fd, $ptr); } return $fd; @@ -237,9 +237,9 @@ public static function zval_to_fd_select(Zval $ptr, string $fd_type = 'php_socke // Validate Checks if ($ptr->macro(\ZE::TYPE_P) === \ZE::IS_RESOURCE) { $handle = $ptr()->value->res->handle; - $zval = Resource::get_fd($handle, true); - if ($zval instanceof Zval) - return Resource::get_fd($handle, false, false, true); + $fd = \resource_get_fd($handle); + if (!\is_null($fd)) + return $fd; $zval_fd = \fd_type($fd_type); $fd = $zval_fd(); @@ -287,7 +287,7 @@ public static function zval_to_fd_select(Zval $ptr, string $fd_type = 'php_socke if (\is_cdata($fd)) { $fd = $fd[0]; - $zval_fd->add_pair($ptr, $fd, $handle); + $zval_fd->add_fd_pair($fd, $ptr); } elseif ($fd === -1) { unset($zval_fd); } diff --git a/zend/Types/Resource.php b/zend/Types/Resource.php index cf3d6c6..6a80de1 100644 --- a/zend/Types/Resource.php +++ b/zend/Types/Resource.php @@ -129,36 +129,24 @@ class Resource extends \ZE protected $isZval = false; protected $fd = []; - protected ?int $index = null; - protected ?object $extra = null; - protected ?Zval $zval = null; + protected ?int $file = null; + protected ?object $gc_object = null; /** @var Resource|PhpStream */ protected static $instances = null; public function __destruct() { - if (!\is_null($this->extra)) { - $object = $this->extra; - $this->extra = null; - \zval_del_ref($object); - } - $this->free(); } - public function update(CData $ptr, bool $isOther = false): self + protected function __construct(string $typedef, bool $create = true) { - if ($isOther) { - \FFI::free($this->ze_other_ptr); - $this->ze_other_ptr = null; - $this->ze_other = null; + $this->isZval = false; + if ($create) { + $this->ze_other = \ze_ffi()->new($typedef); + $this->ze_other_ptr = \ffi_ptr($this->ze_other); } - - - $this->ze_other_ptr = $ptr; - - return $this; } public function free(): void @@ -182,56 +170,80 @@ public function __invoke($isZval = true) public function fd(): int { - return $this->index; + return $this->fd[$this->file][0]; } public function clear(int $handle): void { if (!\is_null($this->fd) && isset($this->fd[$handle])) { [$fd, $res] = $this->fd[$handle]; - unset($this->fd[$fd], $this->fd[$res]); - static::$instances[$fd] = static::$instances[$res] = null; - } - - if (\count($this->fd) === 0) { - $this->zval = null; - $this->index = null; + unset($this->fd[$fd], $this->fd[(int)$res]); + + static::$instances[$fd] = null; + $resource = static::$instances[(int)$res]; + static::$instances[(int)$res] = null; + + if (\count($this->fd) === 0) { + \zval_del_ref($resource); + if (!\is_null($this->gc_object)) { + $object = $this->gc_object; + $this->gc_object = null; + \zval_del_ref($object); + } + } } } - public function get_zval(): ?Zval + public function add_object(object $store): self { - return $this->zval; - } - - public function add_object(object $extra): self - { - $this->extra = $extra; + $this->gc_object = $store; return $this; } - public function add_pair(Zval $zval, int $fd1, int $resource1, int $fd0 = null, int $resource0 = null) + /** + * @param integer $fd0 + * @param resource|Zval $resource0 + * @param integer|null $fd1 + * @param resource|Zval $resource1 + * @return self + */ + public function add_fd_pair(int $fd0, $resource0, int $fd1 = null, $resource1 = null): self { - $this->zval = $zval; - $this->index = $fd1; - $this->fd[$fd1] = [$fd1, $resource1]; - $this->fd[$resource1] = [$fd1, $resource1]; - static::$instances[$fd1] = $this; - static::$instances[$resource1] = $this; - if (!\is_null($fd0) && !\is_null($resource0)) { - $this->fd[$fd0] = [$fd0, $resource0]; - $this->fd[$resource0] = [$fd0, $resource0]; - static::$instances[$fd0] = $this; - static::$instances[$resource0] = $this; + if (!$resource0 instanceof Zval && !\is_resource($resource0)) + return \ze_ffi()->zend_error(\E_WARNING, "invalid resource passed"); + + /** @var resource */ + $fd = $resource0 instanceof Zval + ? \zval_native($resource0) + : $resource0; + + $this->file = $fd0; + $this->fd[$fd0] = [$fd0, $fd]; + $this->fd[(int)$fd] = [$fd0, $fd]; + static::$instances[$fd0] = $this; + static::$instances[(int)$fd] = $this; + if (!\is_null($fd1) && !\is_null($resource1)) { + if (!$resource1 instanceof Zval && !\is_resource($resource1)) + return \ze_ffi()->zend_error(\E_WARNING, "invalid resource passed"); + + /** @var resource */ + $resource = $resource1 instanceof Zval + ? \zval_native($resource1) + : $resource1; + + $this->fd[$fd1] = [$fd1, $resource]; + $this->fd[(int)$resource] = [$fd1, $resource]; + static::$instances[$fd1] = $this; + static::$instances[(int)$resource] = $this; } return $this; } - public function get_pair(int $fd): ?int + public function get_pair(int $fd): ?array { - return $this->fd[$fd][0] ?? null; + return $this->fd[$fd] ?? null; } public static function is_valid(int $fd): bool @@ -241,41 +253,42 @@ public static function is_valid(int $fd): bool /** * @param integer $handle - * @param boolean $getZval + * @param boolean $get_Int file descriptor + * @param boolean $getSelf * @param boolean $getPair - * @param boolean $getInt file descriptor - * @return Zval|int|CData|null + * @return self|int|array|CData|null */ - public static function get_fd(int $handle, bool $getZval = false, bool $getPair = false, bool $getInt = false) + public static function get_fd(int $handle, bool $get_Int = true, bool $getPair = false, bool $getSelf = false) { + $resource = null; if (static::is_valid($handle)) { - /** @var Resource|PhpStream */ + /** @var Resource|PhpStream|CData */ $resource = static::$instances[$handle]; - if ($getZval) - return $resource->get_zval(); + if ($getSelf) + return $resource; elseif ($getPair) return $resource->get_pair($handle); - elseif ($getInt) + elseif ($get_Int) return $resource->fd(); - else - return $resource(); + + return $resource(); } - return null; + return $resource; } public static function remove_fd(int $handle): void { - if (static::is_valid($handle)) { + if (isset(static::$instances[$handle])) { /** @var Resource|PhpStream */ $object = static::$instances[$handle]; $object->clear($handle); } } - public static function init(string $type = 'uv_file'): self + public static function init(string $type = 'php_socket_t', bool $create = true): self { - return new static($type, false); + return new static($type, $create); } } }