Skip to content

Commit 5a6db76

Browse files
authored
Merge pull request #1 from lisachenko/feature/native-immutable-object
Rewrite immutalbe object in PHP with native handlers
2 parents a05248e + ef6b3f3 commit 5a6db76

19 files changed

+274
-602
lines changed

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
],
1212
"minimum-stability": "stable",
1313
"require": {
14-
"php": ">=7.2.0"
14+
"lisachenko/z-engine": "^0.7.1"
1515
},
1616
"require-dev": {
1717
"phpunit/phpunit": "^7.5"

phpunit.xml.dist

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<testsuites>
2323
<testsuite name="Immutable object Test Suite">
2424
<directory>./tests/</directory>
25+
<directory suffix=".phpt">./tests/</directory>
2526
</testsuite>
2627
</testsuites>
2728

src/ImmutableHandler.php

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
/**
3+
* Immutable object library
4+
*
5+
* @copyright Copyright 2020 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+
declare(strict_types=1);
11+
12+
namespace Immutable;
13+
14+
use Closure;
15+
use ReflectionMethod;
16+
use ZEngine\ClassExtension\Hook\GetPropertyPointerHook;
17+
use ZEngine\ClassExtension\Hook\InterfaceGetsImplementedHook;
18+
use ZEngine\ClassExtension\Hook\UnsetPropertyHook;
19+
use ZEngine\ClassExtension\Hook\WritePropertyHook;
20+
use ZEngine\ClassExtension\ObjectCreateTrait;
21+
use ZEngine\ClassExtension\ObjectGetPropertyPointerInterface;
22+
use ZEngine\ClassExtension\ObjectUnsetPropertyInterface;
23+
use ZEngine\ClassExtension\ObjectWritePropertyInterface;
24+
use ZEngine\Core;
25+
use ZEngine\Reflection\ReflectionClass;
26+
27+
/**
28+
* This ImmutableHandler controls the behaviour of
29+
*/
30+
final class ImmutableHandler implements
31+
ObjectWritePropertyInterface,
32+
ObjectGetPropertyPointerInterface,
33+
ObjectUnsetPropertyInterface
34+
{
35+
public static function install(): void
36+
{
37+
$handler = Closure::fromCallable([self::class, '__interfaceImplemented']);
38+
$interface = new ReflectionClass(ImmutableInterface::class);
39+
$interface->setInterfaceGetsImplementedHandler($handler);
40+
}
41+
42+
public static function __interfaceImplemented(InterfaceGetsImplementedHook $hook): int
43+
{
44+
$objectCreateHandler = (new ReflectionMethod(ObjectCreateTrait::class, '__init'))->getClosure();
45+
$objectFieldWriteHandler = (new ReflectionMethod(self::class, '__fieldWrite'))->getClosure();
46+
$objectFieldPointerHandler = (new ReflectionMethod(self::class, '__fieldPointer'))->getClosure();
47+
$objectFieldUnsetHandler = (new ReflectionMethod(self::class, '__fieldUnset'))->getClosure();
48+
49+
$implementor = $hook->getClass();
50+
$implementor->setCreateObjectHandler($objectCreateHandler);
51+
$implementor->setWritePropertyHandler($objectFieldWriteHandler);
52+
$implementor->setGetPropertyPointerHandler($objectFieldPointerHandler);
53+
$implementor->setUnsetPropertyHandler($objectFieldUnsetHandler);
54+
55+
return Core::SUCCESS;
56+
}
57+
58+
/**
59+
* @inheritDoc
60+
*/
61+
public static function __fieldWrite(WritePropertyHook $hook)
62+
{
63+
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS|DEBUG_BACKTRACE_PROVIDE_OBJECT, 3);
64+
$frame = $trace[2] ?? [];
65+
if (!isset($frame['class'])) {
66+
throw new \LogicException('Immutable object could be modified only in constructor or static methods');
67+
}
68+
try {
69+
$refMethod = new ReflectionMethod($frame['class'], $frame['function']);
70+
} catch(\ReflectionException $e) {
71+
$refMethod = null;
72+
}
73+
if (!$refMethod || !($refMethod->isConstructor() || $refMethod->isStatic())) {
74+
throw new \LogicException('Immutable object could be modified only in constructor or static methods');
75+
}
76+
77+
return $hook->getValue();
78+
}
79+
80+
/**
81+
* @inheritDoc
82+
*/
83+
public static function __fieldPointer(GetPropertyPointerHook $hook)
84+
{
85+
throw new \LogicException("Indirect modification of immutable field is restricted");
86+
}
87+
88+
/**
89+
* @inheritDoc
90+
*/
91+
public static function __fieldUnset(UnsetPropertyHook $hook): void
92+
{
93+
throw new \LogicException("Unset of immutable field is restricted");
94+
}
95+
}

src/ImmutableInterface.php

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
/**
3+
* Immutable object library
4+
*
5+
* @copyright Copyright 2020 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+
declare(strict_types=1);
11+
12+
namespace Immutable;
13+
14+
/**
15+
* This ImmutableInterface marker makes any custom PHP class immutable during implementation.
16+
*/
17+
interface ImmutableInterface
18+
{
19+
20+
}

src/ImmutableTrait.php

-166
This file was deleted.

0 commit comments

Comments
 (0)