Skip to content

Commit e8e48bb

Browse files
authored
Merge pull request #20 from lisachenko/feature/user-class-handlers
[Feature] Implement userland object handlers
2 parents dc93cfe + 08703fa commit e8e48bb

11 files changed

+798
-3
lines changed

README.md

+152
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,155 @@ $handle = spl_object_id($instance);
117117
$objectEntry = Core::$executor->objectStore[$handle];
118118
var_dump($objectEntry);
119119
```
120+
121+
Object Extensions API
122+
---------------------
123+
124+
With the help of `z-engine` library it is possible to overload standard operators for your classes without diving deep
125+
into the PHP engine implementation. For example, let's say you want to define native matrix operators and use it:
126+
127+
```php
128+
<?php
129+
130+
use ZEngine\ClassExtension\ObjectCastInterface;
131+
use ZEngine\ClassExtension\ObjectCompareValuesInterface;
132+
use ZEngine\ClassExtension\ObjectCreateInterface;
133+
use ZEngine\ClassExtension\ObjectCreateTrait;
134+
use ZEngine\ClassExtension\ObjectDoOperationInterface;
135+
136+
class Matrix implements
137+
ObjectCreateInterface,
138+
ObjectCompareValuesInterface,
139+
ObjectDoOperationInterface,
140+
ObjectCastInterface
141+
{
142+
use ObjectCreateTrait;
143+
144+
// ...
145+
}
146+
$a = new Matrix([10, 20, 30]);
147+
$b = new Matrix([1, 2, 3]);
148+
$c = $a + $b; // Matrix([11, 22, 33])
149+
$c *= 2; // Matrix([22, 44, 66])
150+
```
151+
152+
There are two ways of activating custom handlers.
153+
First way is to implement several system interfaces like
154+
`ObjectCastInterface`, `ObjectCompareValuesInterface`, `ObjectCreateInterface` and `ObjectDoOperationInterface`. After
155+
that you should create an instance of `ReflectionClass` provided by this package and call `installExtensionHandlers`
156+
method to install extensions:
157+
158+
```php
159+
use ZEngine\Reflection\ReflectionClass as ReflectionClassEx;
160+
161+
// ... initialization logic
162+
163+
$refClass = new ReflectionClassEx(Matrix::class);
164+
$refClass->installExtensionHandlers();
165+
```
166+
167+
if you don't have an access to the code (eg. vendor), then you can still have an ability to define custom handlers.
168+
You need to define callbacks as closures explicitly and assign them via `set***Handler()` methods in the
169+
`ReflectionClass`.
170+
171+
```php
172+
use ZEngine\ClassExtension\ObjectCreateTrait;
173+
use ZEngine\Reflection\ReflectionClass as ReflectionClassEx;
174+
175+
$refClass = new ReflectionClassEx(Matrix::class);
176+
$handler = Closure::fromCallable([ObjectCreateTrait::class, '__init']);
177+
$refClass->setCreateObjectHandler($handler);
178+
$refClass->setCompareValuesHandler(function ($left, $right) {
179+
if (is_object($left)) {
180+
$left = spl_object_id($left);
181+
}
182+
if (is_object($right)) {
183+
$right = spl_object_id($right);
184+
}
185+
186+
// Just for example, object with bigger object_id is considered bigger that object with smaller object_id
187+
return $left <=> $right;
188+
});
189+
```
190+
191+
Library provides following interfaces:
192+
193+
First one is `ObjectCastInterface` which provides a hook for handling casting a class instance to scalars. Typical
194+
examples are following: 1) explicit `$value = (int) $objectInstance` or implicit: `$value = 10 + $objectInstance;` in
195+
the case when `do_operation` handler is not installed. Please note, that this handler doesn't handle casting to `array`
196+
type as it is implemented in a different way.
197+
198+
```php
199+
<?php
200+
201+
/**
202+
* Interface ObjectCastInterface allows to cast given object to scalar values, like integer, floats, etc
203+
*/
204+
interface ObjectCastInterface
205+
{
206+
/**
207+
* Performs casting of given object to another value
208+
*
209+
* @param object $instance Instance of object that should be casted
210+
* @param int $typeTo Type of casting, @see ReflectionValue::IS_* constants
211+
*
212+
* @return mixed Casted value
213+
*/
214+
public static function __cast(object $instance, int $typeTo);
215+
}
216+
```
217+
218+
Next `ObjectCompareValuesInterface` interface is used to control the comparison logic. For example, you can compare
219+
two objects or even compare object with scalar values: `if ($object > 10 || $object < $anotherObject)`
220+
221+
```php
222+
<?php
223+
224+
/**
225+
* Interface ObjectCompareValuesInterface allows to perform comparison of objects
226+
*/
227+
interface ObjectCompareValuesInterface
228+
{
229+
/**
230+
* Performs comparison of given object with another value
231+
*
232+
* @param mixed $one First side of operation
233+
* @param mixed $another Another side of operation
234+
*
235+
* @return int Result of comparison: 1 is greater, -1 is less, 0 is equal
236+
*/
237+
public static function __compare($one, $another): int;
238+
}
239+
```
240+
Handler should check arguments (one of them should be an instance of your class) and return integer result -1..1. Where
241+
1 is greater, -1 is less and 0 is equal.
242+
243+
The interface `ObjectDoOperationInterface` is the most powerful one because it gives you control over math operators
244+
applied to your object (such as ADD, SUB, MUL, DIV, POW, etc).
245+
246+
```php
247+
<?php
248+
249+
/**
250+
* Interface ObjectDoOperationInterface allows to perform math operations (aka operator overloading) on object
251+
*/
252+
interface ObjectDoOperationInterface
253+
{
254+
/**
255+
* Performs casting of given object to another value
256+
*
257+
* @param int $opCode Operation code
258+
* @param mixed $left left side of operation
259+
* @param mixed $right Right side of operation
260+
*
261+
* @return mixed Result of operation value
262+
*/
263+
public static function __doOperation(int $opCode, $left, $right);
264+
}
265+
```
266+
This handler receives an opcode (see `OpCode::*` constants) and two arguments (one of them is an instance of class) and
267+
returns a value for that operation. In this handler you can return a new instance of your object to have a chain of
268+
immutable instances of objects.
269+
270+
Important reminder: you **MUST** install the `create_object` handler first in order to install hooks in runtime. Also
271+
you can not install the `create_object` handler for the object if it is internal one.

include/engine_x64_nts.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -1108,4 +1108,7 @@ ZEND_API user_opcode_handler_t zend_get_user_opcode_handler(zend_uchar opcode);
11081108
/**
11091109
* Zend inheritance API
11101110
*/
1111-
ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, zend_bool checked);
1111+
ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, zend_bool checked);
1112+
ZEND_API zend_object ZEND_FASTCALL *zend_objects_new(zend_class_entry *ce);
1113+
ZEND_API void ZEND_FASTCALL zend_object_std_init(zend_object *object, zend_class_entry *ce);
1114+
ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type);
+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
/**
3+
* Z-Engine framework
4+
*
5+
* @copyright Copyright 2019, Lisachenko Alexander <[email protected]>
6+
*
7+
* This source file is subject to the license that is bundled
8+
* with this source code in the file LICENSE.
9+
*
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace ZEngine\ClassExtension;
14+
15+
/**
16+
* Interface ObjectCastInterface allows to cast given object to scalar values, like integer, floats, etc
17+
*/
18+
interface ObjectCastInterface
19+
{
20+
/**
21+
* Performs casting of given object to another value
22+
*
23+
* @param object $instance Instance of object that should be casted
24+
* @param int $typeTo Type of casting, @see ReflectionValue::IS_* constants
25+
*
26+
* @return mixed Casted value
27+
*/
28+
public static function __cast(object $instance, int $typeTo);
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
/**
3+
* Z-Engine framework
4+
*
5+
* @copyright Copyright 2019, Lisachenko Alexander <[email protected]>
6+
*
7+
* This source file is subject to the license that is bundled
8+
* with this source code in the file LICENSE.
9+
*
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace ZEngine\ClassExtension;
14+
15+
/**
16+
* Interface ObjectCompareValuesInterface allows to perform comparison of objects
17+
*/
18+
interface ObjectCompareValuesInterface
19+
{
20+
/**
21+
* Performs comparison of given object with another value
22+
*
23+
* @param mixed $one First side of operation
24+
* @param mixed $another Another side of operation
25+
*
26+
* @return int Result of comparison: 1 is greater, -1 is less, 0 is equal
27+
*/
28+
public static function __compare($one, $another): int;
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
/**
3+
* Z-Engine framework
4+
*
5+
* @copyright Copyright 2019, Lisachenko Alexander <[email protected]>
6+
*
7+
* This source file is subject to the license that is bundled
8+
* with this source code in the file LICENSE.
9+
*
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace ZEngine\ClassExtension;
14+
15+
use Closure;
16+
use FFI\CData;
17+
18+
/**
19+
* Interface ObjectCreateInterface allows to hook into the object initialization process (eg new FooBar())
20+
*/
21+
interface ObjectCreateInterface
22+
{
23+
/**
24+
* Performs low-level initialization of object during new instances creation
25+
*
26+
* @param CData $classType Class type to initialize (zend_class_entry)
27+
* @param Closure $initializer Original initializer that accepts a zend_class_entry and creates a new zend_object
28+
*
29+
* @return CData Pointer to the zend_object instance
30+
*/
31+
public static function __init(CData $classType, Closure $initializer): CData;
32+
}
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
/**
3+
* Z-Engine framework
4+
*
5+
* @copyright Copyright 2019, Lisachenko Alexander <[email protected]>
6+
*
7+
* This source file is subject to the license that is bundled
8+
* with this source code in the file LICENSE.
9+
*
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace ZEngine\ClassExtension;
14+
15+
use Closure;
16+
use FFI\CData;
17+
18+
/**
19+
* Trait ObjectCreateTrait contains default hook implementation for object initialization
20+
*/
21+
trait ObjectCreateTrait
22+
{
23+
/**
24+
* Performs low-level initialization of object during new instances creation
25+
*
26+
* @param CData $classType Class type to initialize (zend_class_entry)
27+
* @param Closure $initializer Original initializer that accepts a zend_class_entry and creates a new zend_object
28+
*
29+
* @return CData Pointer to the zend_object instance
30+
*/
31+
public static function __init(CData $classType, Closure $initializer): CData
32+
{
33+
return $initializer($classType);
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
/**
3+
* Z-Engine framework
4+
*
5+
* @copyright Copyright 2019, Lisachenko Alexander <[email protected]>
6+
*
7+
* This source file is subject to the license that is bundled
8+
* with this source code in the file LICENSE.
9+
*
10+
*/
11+
declare(strict_types=1);
12+
13+
namespace ZEngine\ClassExtension;
14+
15+
/**
16+
* Interface ObjectDoOperationInterface allows to perform math operations (aka operator overloading) on object
17+
*/
18+
interface ObjectDoOperationInterface
19+
{
20+
/**
21+
* Performs casting of given object to another value
22+
*
23+
* @param int $opCode Operation code
24+
* @param mixed $left left side of operation
25+
* @param mixed $right Right side of operation
26+
*
27+
* @return mixed Result of operation value
28+
*/
29+
public static function __doOperation(int $opCode, $left, $right);
30+
}

src/Core.php

+8
Original file line numberDiff line numberDiff line change
@@ -366,4 +366,12 @@ public static function getAlignedSize(int $size): int
366366

367367
return $size;
368368
}
369+
370+
/**
371+
* Returns standard object handlers
372+
*/
373+
public static function getStandardObjectHandlers(): CData
374+
{
375+
return self::$engine->std_object_handlers;
376+
}
369377
}

0 commit comments

Comments
 (0)