Skip to content

Commit

Permalink
Add support for anonymous classes (#152)
Browse files Browse the repository at this point in the history
* Initial anonymous class support

* Fixed array references

* Fixed

* Updated methods

* Renamed to ReflectionClass

* Added basic test

* Added complex test

* Fixed scope

* Fixed scope for anonymous

* Added closure from anonymous method

* Added basic static trait test inside anonymous

* Updated changelog and readme

* Updated

* Added unserialization test
  • Loading branch information
sorinsarca authored Jan 7, 2025
1 parent 847d3c2 commit 6b8e419
Show file tree
Hide file tree
Showing 23 changed files with 1,449 additions and 673 deletions.
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
CHANGELOG
---------

### v4.2.0, 2025.01.07

#### Changes

- Added support for anonymous classes (also supports closures bound to anonymous classes)
- Fixed closure scope for some edge cases
- Improved parsers

#### Internal changes

Added classes

- `AbastractInfo`
- `AbstractParser`
- `AnonymousClassInfo`
- `AnonymousClassParser`
- `ReflectionClass`
- `CodeStream`

Removed

- `ClosureStream` (replaced by `CodeStream`)
- `ClassInfo` (replace by `ReflectionClass`)

### v4.1.0, 2025.01.05

#### Changes
Expand Down
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,28 @@ Serialize closures, serialize anything
```php
use function Opis\Closure\{serialize, unserialize};

$serialized = serialize(fn() => "hello!");
$serialized = serialize(fn() => "hello from closure!");
$greet = unserialize($serialized);

echo $greet(); // hello
echo $greet(); // hello from closure!
```

> [!IMPORTANT]
> Starting with version 4.2, **Opis Closure** supports serialization of anonymous classes.
```php
use function Opis\Closure\{serialize, unserialize};

$serialized = serialize(new class("hello from anonymous class!") {
public function __construct(private string $message) {}

public function greet(): string {
return $this->message;
}
});

$object = unserialize($serialized);
echo $object->greet(); // hello from anonymous class!
```

_A full rewrite was necessary to keep this project compatible with the PHP's new features, such as attributes, enums,
Expand Down Expand Up @@ -59,7 +77,7 @@ Or you could directly reference it into your `composer.json` file as a dependenc
```json
{
"require": {
"opis/closure": "^4.1"
"opis/closure": "^4.2"
}
}
```
Expand Down
119 changes: 119 additions & 0 deletions src/AbstractInfo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace Opis\Closure;

abstract class AbstractInfo
{
protected ?string $key = null;

protected string $header;

protected string $body;

public function __construct(string $header, string $body)
{
$this->header = $header;
$this->body = $body;
}

abstract public function getFactoryPHP(bool $phpTag = true): string;
abstract public function getIncludePHP(bool $phpTag = true): string;

/**
* Unique info key
* @return string
*/
final public function key(): string
{
if (!isset($this->key)) {
$this->key = self::createKey($this->header, $this->body);
// save it to cache
self::$cache[$this->key] = $this;
}
return $this->key;
}

final public function url(): string
{
return CodeStream::STREAM_PROTO . '://' . static::name() . '/' . $this->key();
}

public function __serialize(): array
{
$data = [
"key" => $this->key()
];
if ($this->header) {
$data["header"] = $this->header;
}
$data["body"] = $this->body;
return $data;
}

public function __unserialize(array $data): void
{
$key = $this->key = $data["key"];
// in v4.0.0 header was named imports, handle that case too
$this->header = $data["header"] ?? $data["imports"] ?? "";
$this->body = $data["body"];

// populate cache on deserialization
if ($key && !isset(self::$cache[$key])) {
// save it to cache
self::$cache[$key] = $this;
}
}

/**
* @var static[]
*/
private static array $cache = [];

/**
* @var \ReflectionClass[]
*/
private static array $reflector = [];

/**
* @return string Unique short name
*/
abstract public static function name(): string;

/**
* Loads info from cache or rebuilds from serialized data
* @param array $data
* @return static
*/
final public static function load(array $data): static
{
$key = $data["key"] ?? null;
if ($key && isset(self::$cache[$key])) {
// found in cache
return self::$cache[$key];
}

/** @var static $obj */
$obj = (self::$reflector[static::name()] ??= new \ReflectionClass(static::class))->newInstanceWithoutConstructor();

$obj->__unserialize($data);

return $obj;
}

final public static function clear(): void
{
self::$cache = [];
}

final public static function resolve(string $key): ?static
{
return self::$cache[$key] ?? null;
}

final public static function createKey(string $header, string $body): string
{
// yes, there was a mistake in params order, keep the body first
$code = "$body\n$header";
return $code === "\n" ? "" : md5($code);
}
}
Loading

0 comments on commit 6b8e419

Please sign in to comment.