https://github.com/rcalicdan/serializer
Serializer utility tool for serializing callable for IPC and parallel execution system built on top of opis/closure
https://github.com/rcalicdan/serializer
ipc serialization-library
Last synced: 5 months ago
JSON representation
Serializer utility tool for serializing callable for IPC and parallel execution system built on top of opis/closure
- Host: GitHub
- URL: https://github.com/rcalicdan/serializer
- Owner: rcalicdan
- License: mit
- Created: 2025-12-30T06:34:31.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2025-12-30T15:30:12.000Z (6 months ago)
- Last Synced: 2026-01-03T06:26:44.642Z (6 months ago)
- Topics: ipc, serialization-library
- Language: PHP
- Homepage:
- Size: 27.3 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE.md
Awesome Lists containing this project
README
# Callback Serializer
A PHP library for serializing and unserializing various types of callbacks, including closures, static methods, instance methods, and more. This library is primarily designed for parallel execution and inter-process communication (IPC) serialization, automatically detecting callback types and using the appropriate serialization strategy.
## Installation
```bash
composer require rcalicdan/serializer
```
## Requirements
- PHP 8.3 or higher
- opis/closure library (automatically installed as dependency)
## Overview
The Callback Serializer library provides a robust solution for serializing PHP callbacks, particularly useful for:
- **Parallel execution libraries** that need to pass callbacks between processes
- **Inter-process communication (IPC)** where callbacks must be serialized and transmitted
- Queue systems that need to serialize job callbacks
- Event systems that persist event listeners
- Workflow engines that save state
The library **automatically detects the callback type** and uses the appropriate serialization strategy, removing the need for manual callback type checking.
## Basic Usage
### Static API (Recommended)
The static `CallbackSerializer` class provides a convenient singleton-based API:
```php
use Rcalicdan\Serializer\CallbackSerializer;
// Serialize a closure
$closure = function ($x) {
return $x * 2;
};
$serialized = CallbackSerializer::serialize($closure);
// Unserialize and execute
$callback = CallbackSerializer::unserialize($serialized);
$result = $callback(5); // Returns: 10
// Check if callback can be serialized
if (CallbackSerializer::canSerialize($closure)) {
$serialized = CallbackSerializer::serialize($closure);
}
```
### Non-Static API
For applications requiring multiple independent serializer instances or more control, use the `CallbackSerializationManager` directly:
```php
use Rcalicdan\Serializer\CallbackSerializationManager;
// Create a manager instance
$manager = new CallbackSerializationManager();
// Serialize a callback
$closure = function ($x) {
return $x * 2;
};
$serialized = $manager->serializeCallback($closure);
// Unserialize a callback
$callback = $manager->unserializeCallback($serialized);
$result = $callback(5); // Returns: 10
// Check if callback can be serialized
if ($manager->canSerializeCallback($closure)) {
$serialized = $manager->serializeCallback($closure);
}
// Get serializer information
$info = $manager->getSerializerInfo();
```
### When to Use Non-Static API
Use `CallbackSerializationManager` directly when you need:
- Multiple independent serializer configurations in the same application
- Different custom serializers for different contexts
- More explicit dependency injection
- Easier unit testing without singleton state
```php
use Rcalicdan\Serializer\CallbackSerializationManager;
class ParallelExecutor
{
private CallbackSerializationManager $serializer;
public function __construct(?CallbackSerializationManager $serializer = null)
{
$this->serializer = $serializer ?? new CallbackSerializationManager();
}
public function executeInParallel(callable $callback, array $data): void
{
$serialized = $this->serializer->serializeCallback($callback);
// Send to child process via IPC
$this->sendToChildProcess($serialized, $data);
}
private function sendToChildProcess(string $serialized, array $data): void
{
// IPC implementation
}
}
```
## Supported Callback Types
The library automatically detects and handles these callback types:
### 1. String Functions
Native PHP functions passed as strings.
```php
$callback = 'strtoupper';
$serialized = CallbackSerializer::serialize($callback);
$callback = CallbackSerializer::unserialize($serialized);
echo $callback('hello'); // Outputs: HELLO
```
**Priority:** 100 (highest)
### 2. Static Methods
Class static methods as array `[ClassName::class, 'methodName']`.
```php
class Calculator
{
public static function add($a, $b)
{
return $a + $b;
}
}
$callback = [Calculator::class, 'add'];
$serialized = CallbackSerializer::serialize($callback);
$callback = CallbackSerializer::unserialize($serialized);
echo $callback(5, 3); // Outputs: 8
```
**Priority:** 90
### 3. Closures
Anonymous functions with variable binding support.
```php
$multiplier = 10;
$closure = function ($x) use ($multiplier) {
return $x * $multiplier;
};
$serialized = CallbackSerializer::serialize($closure);
$callback = CallbackSerializer::unserialize($serialized);
echo $callback(5); // Outputs: 50
```
**Priority:** 80
### 4. Instance Methods
Object instance methods as array `[$object, 'methodName']`.
```php
class Greeter
{
private $greeting = 'Hello';
public function greet($name)
{
return "{$this->greeting}, {$name}!";
}
}
$greeter = new Greeter();
$callback = [$greeter, 'greet'];
$serialized = CallbackSerializer::serialize($callback);
$callback = CallbackSerializer::unserialize($serialized);
echo $callback('World'); // Outputs: Hello, World!
```
**Priority:** 70
### 5. Anonymous Classes
Anonymous class instances with `__invoke` method.
```php
$callback = new class {
public function __invoke($x)
{
return $x * 2;
}
};
$serialized = CallbackSerializer::serialize($callback);
$callback = CallbackSerializer::unserialize($serialized);
echo $callback(5); // Outputs: 10
```
**Priority:** 70
### 6. Invokable Objects
Regular class instances with `__invoke` method.
```php
class Doubler
{
public function __invoke($x)
{
return $x * 2;
}
}
$callback = new Doubler();
$serialized = CallbackSerializer::serialize($callback);
$callback = CallbackSerializer::unserialize($serialized);
echo $callback(5); // Outputs: 10
```
**Priority:** 60
## Context Serialization
Serialize and unserialize context data (arrays) alongside callbacks, useful for passing state in parallel execution scenarios.
### Static API
```php
use Rcalicdan\Serializer\CallbackSerializer;
$context = [
'user_id' => 123,
'settings' => ['theme' => 'dark'],
'callback' => function ($x) {
return $x * 2;
},
];
// Serialize context
$serialized = CallbackSerializer::serializeContext($context);
// Unserialize context
$restored = CallbackSerializer::unserializeContext($serialized);
// Context is fully restored including nested callbacks
echo $restored['callback'](5); // Outputs: 10
// Check if context can be serialized
if (CallbackSerializer::canSerializeContext($context)) {
$serialized = CallbackSerializer::serializeContext($context);
}
```
### Non-Static API
```php
use Rcalicdan\Serializer\CallbackSerializationManager;
$manager = new CallbackSerializationManager();
$context = [
'user_id' => 123,
'settings' => ['theme' => 'dark'],
];
// Serialize context
$serialized = $manager->serializeContext($context);
// Unserialize context
$restored = $manager->unserializeContext($serialized);
// Check if context can be serialized
if ($manager->canSerializeContext($context)) {
$serialized = $manager->serializeContext($context);
}
```
## Use Case: Parallel Execution
Example of using the library for parallel execution with IPC:
```php
use Rcalicdan\Serializer\CallbackSerializer;
// Parent process
$task = function ($data) {
// Heavy computation
return array_sum($data) * 2;
};
$serializedTask = CallbackSerializer::serialize($task);
$serializedContext = CallbackSerializer::serializeContext(['data' => [1, 2, 3, 4, 5]]);
// Send to child process via socket/pipe/shared memory
socket_write($socket, $serializedTask);
socket_write($socket, $serializedContext);
// Child process
$serializedTask = socket_read($socket, 8192);
$serializedContext = socket_read($socket, 8192);
$task = CallbackSerializer::unserialize($serializedTask);
$context = CallbackSerializer::unserializeContext($serializedContext);
$result = $task($context['data']);
// Send result back to parent
```
## Advanced Usage
### Custom Serializers (Static API)
Create custom serializers for specific callback types by implementing the `CallbackSerializerInterface`.
```php
use Rcalicdan\Serializer\Interfaces\CallbackSerializerInterface;
use Rcalicdan\Serializer\Exceptions\SerializationException;
use Rcalicdan\Serializer\CallbackSerializer;
class CustomSerializer implements CallbackSerializerInterface
{
public function canSerialize(mixed $callback): bool
{
// Check if this serializer can handle the callback
return $callback instanceof MyCustomCallable;
}
public function serialize(mixed $callback): string
{
// Serialize the callback
if (!$this->canSerialize($callback)) {
throw new SerializationException('Cannot serialize this callback');
}
return json_encode(['custom' => $callback->getData()]);
}
public function unserialize(string $serialized): mixed
{
// Unserialize the callback
$data = json_decode($serialized, true);
return new MyCustomCallable($data['custom']);
}
public function getPriority(): int
{
// Higher priority = checked first (100 = highest)
return 95;
}
}
// Register custom serializer
$customSerializer = new CustomSerializer();
CallbackSerializer::addSerializer($customSerializer);
// Now your custom serializer will be used automatically
$callback = new MyCustomCallable();
$serialized = CallbackSerializer::serialize($callback);
```
### Custom Serializers (Non-Static API)
```php
use Rcalicdan\Serializer\CallbackSerializationManager;
$manager = new CallbackSerializationManager();
// Add custom serializer
$customSerializer = new CustomSerializer();
$manager->addSerializer($customSerializer);
// Use the manager with custom serializer
$callback = new MyCustomCallable();
$serialized = $manager->serializeCallback($callback);
```
### Get Serializer Information
#### Static API
```php
$info = CallbackSerializer::getSerializerInfo();
foreach ($info as $serializer) {
echo "Class: {$serializer['class']}\n";
echo "Name: {$serializer['name']}\n";
echo "Priority: {$serializer['priority']}\n\n";
}
```
#### Non-Static API
```php
$manager = new CallbackSerializationManager();
$info = $manager->getSerializerInfo();
foreach ($info as $serializer) {
echo "Class: {$serializer['class']}\n";
echo "Name: {$serializer['name']}\n";
echo "Priority: {$serializer['priority']}\n\n";
}
```
Output:
```
Class: Rcalicdan\Serializer\Serializers\StringFunctionSerializer
Name: StringFunctionSerializer
Priority: 100
Class: Rcalicdan\Serializer\Serializers\StaticMethodSerializer
Name: StaticMethodSerializer
Priority: 90
Class: Rcalicdan\Serializer\Serializers\ClosureSerializer
Name: ClosureSerializer
Priority: 80
...
```
### Reset Singleton (Testing)
Useful for resetting the serializer state in unit tests when using the static API.
```php
use Rcalicdan\Serializer\CallbackSerializer;
// Reset the singleton instance
CallbackSerializer::reset();
// Fresh instance with default serializers
$callback = function () {};
$serialized = CallbackSerializer::serialize($callback);
```
## Exception Handling
The library throws `SerializationException` when serialization or unserialization fails.
```php
use Rcalicdan\Serializer\CallbackSerializer;
use Rcalicdan\Serializer\Exceptions\SerializationException;
try {
$callback = /* some callback */;
$serialized = CallbackSerializer::serialize($callback);
} catch (SerializationException $e) {
echo "Serialization failed: " . $e->getMessage();
}
try {
$callback = CallbackSerializer::unserialize($serialized);
} catch (SerializationException $e) {
echo "Unserialization failed: " . $e->getMessage();
}
```
## Architecture
### Automatic Callback Type Detection
The library uses a priority-based system to automatically detect and handle different callback types. When you call `serialize()`, the library:
1. Iterates through registered serializers in priority order (highest first)
2. Checks if each serializer can handle the callback using `canSerialize()`
3. Uses the first matching serializer to perform the serialization
4. Throws `SerializationException` if no serializer matches
This automatic detection means you never need to manually determine the callback type.
### Serializer Priority System
Serializers are checked in order of priority (highest first):
1. **StringFunctionSerializer (100)** - Fastest, no complex serialization needed
2. **StaticMethodSerializer (90)** - Simple string serialization
3. **ClosureSerializer (80)** - Uses opis/closure for complex serialization
4. **InstanceMethodSerializer (70)** - Serializes object instances
5. **AnonymousClassSerializer (70)** - Handles anonymous classes
6. **InvokableObjectSerializer (60)** - Catches remaining invokable objects
### Singleton Pattern (Static API)
The static `CallbackSerializer` class uses a singleton pattern to maintain a single instance of `CallbackSerializationManager`, ensuring consistent serializer registration across your application.
### Direct Instantiation (Non-Static API)
The `CallbackSerializationManager` can be instantiated directly for scenarios requiring multiple independent configurations or better testability.
## Testing
### Testing with Static API
```php
use PHPUnit\Framework\TestCase;
use Rcalicdan\Serializer\CallbackSerializer;
class CallbackSerializerTest extends TestCase
{
protected function setUp(): void
{
// Reset singleton between tests
CallbackSerializer::reset();
}
public function test_can_serialize_closure(): void
{
$closure = function ($x) {
return $x * 2;
};
$this->assertTrue(CallbackSerializer::canSerialize($closure));
$serialized = CallbackSerializer::serialize($closure);
$unserialized = CallbackSerializer::unserialize($serialized);
$this->assertEquals(10, $unserialized(5));
}
}
```
### Testing with Non-Static API
```php
use PHPUnit\Framework\TestCase;
use Rcalicdan\Serializer\CallbackSerializationManager;
class CallbackSerializationManagerTest extends TestCase
{
private CallbackSerializationManager $manager;
protected function setUp(): void
{
// Create fresh instance for each test
$this->manager = new CallbackSerializationManager();
}
public function test_can_serialize_closure(): void
{
$closure = function ($x) {
return $x * 2;
};
$this->assertTrue($this->manager->canSerializeCallback($closure));
$serialized = $this->manager->serializeCallback($closure);
$unserialized = $this->manager->unserializeCallback($serialized);
$this->assertEquals(10, $unserialized(5));
}
}
```
## Limitations
- Closures with resources (file handles, database connections) cannot be serialized
- Some objects with internal state may not serialize correctly
- Callbacks referencing unavailable classes will fail on unserialization
- Serialized callbacks may break if the referenced code changes between serialization and unserialization
## License
MIT License
## Contributing
Contributions are welcome! Please submit pull requests or open issues on GitHub.
## Credits
This library uses [opis/closure](https://github.com/opis/closure) for closure serialization.