Skip to content

Commit

Permalink
Merge pull request #20 from lisachenko/feature/user-class-handlers
Browse files Browse the repository at this point in the history
[Feature] Implement userland object handlers
  • Loading branch information
lisachenko committed Dec 10, 2019
2 parents dc93cfe + 08703fa commit e8e48bb
Show file tree
Hide file tree
Showing 11 changed files with 798 additions and 3 deletions.
152 changes: 152 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,155 @@ $handle = spl_object_id($instance);
$objectEntry = Core::$executor->objectStore[$handle];
var_dump($objectEntry);
```

Object Extensions API
---------------------

With the help of `z-engine` library it is possible to overload standard operators for your classes without diving deep
into the PHP engine implementation. For example, let's say you want to define native matrix operators and use it:

```php
<?php

use ZEngine\ClassExtension\ObjectCastInterface;
use ZEngine\ClassExtension\ObjectCompareValuesInterface;
use ZEngine\ClassExtension\ObjectCreateInterface;
use ZEngine\ClassExtension\ObjectCreateTrait;
use ZEngine\ClassExtension\ObjectDoOperationInterface;

class Matrix implements
ObjectCreateInterface,
ObjectCompareValuesInterface,
ObjectDoOperationInterface,
ObjectCastInterface
{
use ObjectCreateTrait;

// ...
}
$a = new Matrix([10, 20, 30]);
$b = new Matrix([1, 2, 3]);
$c = $a + $b; // Matrix([11, 22, 33])
$c *= 2; // Matrix([22, 44, 66])
```

There are two ways of activating custom handlers.
First way is to implement several system interfaces like
`ObjectCastInterface`, `ObjectCompareValuesInterface`, `ObjectCreateInterface` and `ObjectDoOperationInterface`. After
that you should create an instance of `ReflectionClass` provided by this package and call `installExtensionHandlers`
method to install extensions:

```php
use ZEngine\Reflection\ReflectionClass as ReflectionClassEx;

// ... initialization logic

$refClass = new ReflectionClassEx(Matrix::class);
$refClass->installExtensionHandlers();
```

if you don't have an access to the code (eg. vendor), then you can still have an ability to define custom handlers.
You need to define callbacks as closures explicitly and assign them via `set***Handler()` methods in the
`ReflectionClass`.

```php
use ZEngine\ClassExtension\ObjectCreateTrait;
use ZEngine\Reflection\ReflectionClass as ReflectionClassEx;

$refClass = new ReflectionClassEx(Matrix::class);
$handler = Closure::fromCallable([ObjectCreateTrait::class, '__init']);
$refClass->setCreateObjectHandler($handler);
$refClass->setCompareValuesHandler(function ($left, $right) {
if (is_object($left)) {
$left = spl_object_id($left);
}
if (is_object($right)) {
$right = spl_object_id($right);
}

// Just for example, object with bigger object_id is considered bigger that object with smaller object_id
return $left <=> $right;
});
```

Library provides following interfaces:

First one is `ObjectCastInterface` which provides a hook for handling casting a class instance to scalars. Typical
examples are following: 1) explicit `$value = (int) $objectInstance` or implicit: `$value = 10 + $objectInstance;` in
the case when `do_operation` handler is not installed. Please note, that this handler doesn't handle casting to `array`
type as it is implemented in a different way.

```php
<?php

/**
* Interface ObjectCastInterface allows to cast given object to scalar values, like integer, floats, etc
*/
interface ObjectCastInterface
{
/**
* Performs casting of given object to another value
*
* @param object $instance Instance of object that should be casted
* @param int $typeTo Type of casting, @see ReflectionValue::IS_* constants
*
* @return mixed Casted value
*/
public static function __cast(object $instance, int $typeTo);
}
```

Next `ObjectCompareValuesInterface` interface is used to control the comparison logic. For example, you can compare
two objects or even compare object with scalar values: `if ($object > 10 || $object < $anotherObject)`

```php
<?php

/**
* Interface ObjectCompareValuesInterface allows to perform comparison of objects
*/
interface ObjectCompareValuesInterface
{
/**
* Performs comparison of given object with another value
*
* @param mixed $one First side of operation
* @param mixed $another Another side of operation
*
* @return int Result of comparison: 1 is greater, -1 is less, 0 is equal
*/
public static function __compare($one, $another): int;
}
```
Handler should check arguments (one of them should be an instance of your class) and return integer result -1..1. Where
1 is greater, -1 is less and 0 is equal.

The interface `ObjectDoOperationInterface` is the most powerful one because it gives you control over math operators
applied to your object (such as ADD, SUB, MUL, DIV, POW, etc).

```php
<?php

/**
* Interface ObjectDoOperationInterface allows to perform math operations (aka operator overloading) on object
*/
interface ObjectDoOperationInterface
{
/**
* Performs casting of given object to another value
*
* @param int $opCode Operation code
* @param mixed $left left side of operation
* @param mixed $right Right side of operation
*
* @return mixed Result of operation value
*/
public static function __doOperation(int $opCode, $left, $right);
}
```
This handler receives an opcode (see `OpCode::*` constants) and two arguments (one of them is an instance of class) and
returns a value for that operation. In this handler you can return a new instance of your object to have a chain of
immutable instances of objects.

Important reminder: you **MUST** install the `create_object` handler first in order to install hooks in runtime. Also
you can not install the `create_object` handler for the object if it is internal one.
5 changes: 4 additions & 1 deletion include/engine_x64_nts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1108,4 +1108,7 @@ ZEND_API user_opcode_handler_t zend_get_user_opcode_handler(zend_uchar opcode);
/**
* Zend inheritance API
*/
ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, zend_bool checked);
ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, zend_bool checked);
ZEND_API zend_object ZEND_FASTCALL *zend_objects_new(zend_class_entry *ce);
ZEND_API void ZEND_FASTCALL zend_object_std_init(zend_object *object, zend_class_entry *ce);
ZEND_API void object_properties_init(zend_object *object, zend_class_entry *class_type);
29 changes: 29 additions & 0 deletions src/ClassExtension/ObjectCastInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
/**
* Z-Engine framework
*
* @copyright Copyright 2019, Lisachenko Alexander <lisachenko.it@gmail.com>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*
*/
declare(strict_types=1);

namespace ZEngine\ClassExtension;

/**
* Interface ObjectCastInterface allows to cast given object to scalar values, like integer, floats, etc
*/
interface ObjectCastInterface
{
/**
* Performs casting of given object to another value
*
* @param object $instance Instance of object that should be casted
* @param int $typeTo Type of casting, @see ReflectionValue::IS_* constants
*
* @return mixed Casted value
*/
public static function __cast(object $instance, int $typeTo);
}
29 changes: 29 additions & 0 deletions src/ClassExtension/ObjectCompareValuesInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
/**
* Z-Engine framework
*
* @copyright Copyright 2019, Lisachenko Alexander <lisachenko.it@gmail.com>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*
*/
declare(strict_types=1);

namespace ZEngine\ClassExtension;

/**
* Interface ObjectCompareValuesInterface allows to perform comparison of objects
*/
interface ObjectCompareValuesInterface
{
/**
* Performs comparison of given object with another value
*
* @param mixed $one First side of operation
* @param mixed $another Another side of operation
*
* @return int Result of comparison: 1 is greater, -1 is less, 0 is equal
*/
public static function __compare($one, $another): int;
}
32 changes: 32 additions & 0 deletions src/ClassExtension/ObjectCreateInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
/**
* Z-Engine framework
*
* @copyright Copyright 2019, Lisachenko Alexander <lisachenko.it@gmail.com>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*
*/
declare(strict_types=1);

namespace ZEngine\ClassExtension;

use Closure;
use FFI\CData;

/**
* Interface ObjectCreateInterface allows to hook into the object initialization process (eg new FooBar())
*/
interface ObjectCreateInterface
{
/**
* Performs low-level initialization of object during new instances creation
*
* @param CData $classType Class type to initialize (zend_class_entry)
* @param Closure $initializer Original initializer that accepts a zend_class_entry and creates a new zend_object
*
* @return CData Pointer to the zend_object instance
*/
public static function __init(CData $classType, Closure $initializer): CData;
}
35 changes: 35 additions & 0 deletions src/ClassExtension/ObjectCreateTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
/**
* Z-Engine framework
*
* @copyright Copyright 2019, Lisachenko Alexander <lisachenko.it@gmail.com>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*
*/
declare(strict_types=1);

namespace ZEngine\ClassExtension;

use Closure;
use FFI\CData;

/**
* Trait ObjectCreateTrait contains default hook implementation for object initialization
*/
trait ObjectCreateTrait
{
/**
* Performs low-level initialization of object during new instances creation
*
* @param CData $classType Class type to initialize (zend_class_entry)
* @param Closure $initializer Original initializer that accepts a zend_class_entry and creates a new zend_object
*
* @return CData Pointer to the zend_object instance
*/
public static function __init(CData $classType, Closure $initializer): CData
{
return $initializer($classType);
}
}
30 changes: 30 additions & 0 deletions src/ClassExtension/ObjectDoOperationInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php
/**
* Z-Engine framework
*
* @copyright Copyright 2019, Lisachenko Alexander <lisachenko.it@gmail.com>
*
* This source file is subject to the license that is bundled
* with this source code in the file LICENSE.
*
*/
declare(strict_types=1);

namespace ZEngine\ClassExtension;

/**
* Interface ObjectDoOperationInterface allows to perform math operations (aka operator overloading) on object
*/
interface ObjectDoOperationInterface
{
/**
* Performs casting of given object to another value
*
* @param int $opCode Operation code
* @param mixed $left left side of operation
* @param mixed $right Right side of operation
*
* @return mixed Result of operation value
*/
public static function __doOperation(int $opCode, $left, $right);
}
8 changes: 8 additions & 0 deletions src/Core.php
Original file line number Diff line number Diff line change
Expand Up @@ -366,4 +366,12 @@ public static function getAlignedSize(int $size): int

return $size;
}

/**
* Returns standard object handlers
*/
public static function getStandardObjectHandlers(): CData
{
return self::$engine->std_object_handlers;
}
}
Loading

0 comments on commit e8e48bb

Please sign in to comment.