Skip to content

Commit

Permalink
Merge pull request #54 from zumba/multiple-closure-serializers
Browse files Browse the repository at this point in the history
Multiple closure serializers - With opis/closure support
  • Loading branch information
jrbasso authored Jul 17, 2023
2 parents 31d23c1 + c7dd2c5 commit 3d9f1b7
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
restore-keys: |
${{ runner.os }}-php-
- name: Install suggested dependencies
run: composer require jeremeamia/superclosure
run: composer require jeremeamia/superclosure opis/closure
- name: Install dependencies
if: steps.composer-cache.outputs.cache-hit != 'true'
run: composer install --prefer-dist --no-progress --no-suggest
Expand Down
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
## [3.1.0] - 2023-07-18
### Added
- Support of UnitEnum and BackedEnum deserialization (PHP > 8.1)
- Support of UnitEnum and BackedEnum deserialization (PHP > 8.1). Thanks @marcimat
- Support to multiple closure serializers
- Built in closure serializer using opis/closure
### Fixed
- Fixed deprecated with DateTimeImmutable deserialization with PHP 8.2

Expand Down
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Limitations:
This project should not be confused with `JsonSerializable` interface added on PHP 5.4. This interface is used on
`json_encode` to encode the objects. There is no unserialization with this interface, differently from this project.

*Json Serializer requires PHP >= 7.0 and tested until PHP 7.4*
*Json Serializer requires PHP >= 7.0 and tested until PHP 8.2*

## Example

Expand Down Expand Up @@ -98,17 +98,16 @@ $json = $serializer->serialize($data);
```


## Serializing Closures
## Serializing Closure

For serializing PHP closures you have to pass an object implementing `SuperClosure\SerializerInterface`.
This interface is provided by the project [SuperClosure](https://github.com/jeremeamia/super_closure). This
project also provides a closure serializer that implements this interface.
For serializing PHP closures you can either use [OpisClosure](https://github.com/opis/closure) (preferred) or
[SuperClosure](https://github.com/jeremeamia/super_closure) (the project is abandoned, so kept here for backward
compatibility).

Closure serialization has some limitations. Please check the SuperClosure project to check if it fits your
needs.
Closure serialization has some limitations. Please check the OpisClosure or SuperClosure project to check if it fits
your needs.

To use the SuperClosure with JsonSerializer, just pass the SuperClosure object as the first parameter
on JsonSerializer constructor. Example:
To use the OpisClosure with JsonSerializer, just add it to the closure serializer list. Example:

```php
$toBeSerialized = [
Expand All @@ -122,13 +121,15 @@ $toBeSerialized = [
}
];

$superClosure = new SuperClosure\Serializer();
$jsonSerializer = new Zumba\JsonSerializer\JsonSerializer($superClosure);
$jsonSerializer = new \Zumba\JsonSerializer\JsonSerializer();
$jsonSerializer->addClosureSerializer(new \Zumba\JsonSerializer\ClosureSerializer\OpisClosureSerializer());
$serialized = $jsonSerializer->serialize($toBeSerialized);
```

PS: JsonSerializer does not have a hard dependency of SuperClosure. If you want to use both projects
make sure you add both on your composer requirements.
You can load multiple closure serializers in case you are migrating from SuperClosure to OpisClosure for example.

PS: JsonSerializer does not have a hard dependency of OpisClosure or SuperClosure. If you want to use both projects
make sure you add both on your composer requirements and load them with `addClosureSerializer()` method.

## Custom Serializers

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"ext-mbstring": "*"
},
"suggest": {
"jeremeamia/superclosure": "Allow to serialize PHP closures"
"opis/closure": "Allow to serialize PHP closures"
},
"require-dev": {
"phpunit/phpunit": ">=6.0 <10.0"
Expand Down
25 changes: 25 additions & 0 deletions src/JsonSerializer/ClosureSerializer/ClosureSerializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace Zumba\JsonSerializer\ClosureSerializer;

use Closure;

interface ClosureSerializer {

/**
* Serialize a closure
*
* @param Closure $closure
* @return string
*/
public function serialize(Closure $closure);

/**
* Unserialize a closure
*
* @param string $serialized
* @return Closure
*/
public function unserialize($serialized);

}
67 changes: 67 additions & 0 deletions src/JsonSerializer/ClosureSerializer/ClosureSerializerManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

namespace Zumba\JsonSerializer\ClosureSerializer;

class ClosureSerializerManager {

/**
* Closure serializer instances
*
* @var array
*/
protected $closureSerializer = array();

/**
* Prefered closure serializer
*/
protected $preferred = array(
OpisClosureSerializer::class,
SuperClosureSerializer::class
);

/**
* Set closure engine
*
* @param ClosureSerializer $closureSerializer
* @return self
*/
public function addSerializer(ClosureSerializer $closureSerializer)
{
$classname = $closureSerializer::class;
$this->closureSerializer[$classname] = $closureSerializer;
return $this;
}

/**
* Get preferred closure serializer
*
* @return ClosureSerializer|null
*/
public function getPreferredSerializer()
{
if (empty($this->closureSerializer)) {
return null;
}

foreach ($this->preferred as $preferred) {
if (isset($this->closureSerializer[$preferred])) {
return $this->closureSerializer[$preferred];
}
}
return current($this->closureSerializer);
}

/**
* Get closure serializer
*
* @param string $classname
* @return ClosureSerializer|null
*/
public function getSerializer(string $classname)
{
if (isset($this->closureSerializer[$classname])) {
return $this->closureSerializer[$classname];
}
return null;
}
}
32 changes: 32 additions & 0 deletions src/JsonSerializer/ClosureSerializer/OpisClosureSerializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Zumba\JsonSerializer\ClosureSerializer;

use Closure;
use Opis\Closure\SerializableClosure as OpisSerializableClosure;

class OpisClosureSerializer implements ClosureSerializer {

/**
* Serialize a closure
*
* @param Closure $closure
* @return string
*/
public function serialize(Closure $closure)
{
return serialize(new OpisSerializableClosure($closure));
}

/**
* Unserialize a closure
*
* @param string $serialized
* @return Closure
*/
public function unserialize($serialized)
{
return unserialize($serialized)->getClosure();
}

}
49 changes: 49 additions & 0 deletions src/JsonSerializer/ClosureSerializer/SuperClosureSerializer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Zumba\JsonSerializer\ClosureSerializer;

use Closure;
use SuperClosure\SerializerInterface as SuperClosureSerializerInterface;

class SuperClosureSerializer implements ClosureSerializer {

/**
* Closure serializer instance
*
* @var SuperClosureSerializerInterface
*/
protected $serializer;

/**
* Closure serializer instance
*
* @var SuperClosureSerializerInterface
*/
public function __construct(SuperClosureSerializerInterface $serializer)
{
$this->serializer = $serializer;
}

/**
* Serialize a closure
*
* @param Closure $closure
* @return string
*/
public function serialize(Closure $closure)
{
return $this->serializer->serialize($closure);
}

/**
* Unserialize a closure
*
* @param string $serialized
* @return Closure
*/
public function unserialize($serialized)
{
return $this->serializer->unserialize($serialized);
}

}
38 changes: 29 additions & 9 deletions src/JsonSerializer/JsonSerializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ class JsonSerializer
protected $objectMappingIndex = 0;

/**
* Closure serializer instance
* Closure manager
*
* @var ClosureSerializerInterface
* @var ClosureSerializer\ClosureSerializerManager
*/
protected $closureSerializer;
protected $closureManager;

/**
* Map of custom object serializers
Expand All @@ -72,17 +72,33 @@ class JsonSerializer
/**
* Constructor.
*
* @param ClosureSerializerInterface $closureSerializer
* @param ClosureSerializerInterface $closureSerializer This parameter is deprecated and will be removed in 5.0.0. Use addClosureSerializer() instead.
* @param array $customObjectSerializerMap
*/
public function __construct(
ClosureSerializerInterface $closureSerializer = null,
$customObjectSerializerMap = []
) {
$this->closureSerializer = $closureSerializer;
$this->closureManager = new ClosureSerializer\ClosureSerializerManager();
if ($closureSerializer) {
trigger_error(
'Passing a ClosureSerializerInterface to the constructor is deprecated and will be removed in 4.0.0. Use addClosureSerializer() instead.',
E_USER_DEPRECATED
);
$this->addClosureSerializer(new ClosureSerializer\SuperClosureSerializer($closureSerializer));
}

$this->customObjectSerializerMap = (array)$customObjectSerializerMap;
}

/**
* Add a closure serializer
*/
public function addClosureSerializer(ClosureSerializer\ClosureSerializer $closureSerializer)
{
$this->closureManager->addSerializer($closureSerializer);
}

/**
* Serialize the value in JSON
*
Expand Down Expand Up @@ -242,12 +258,14 @@ protected function serializeData($value)
return array_map([$this, __FUNCTION__], $value);
}
if ($value instanceof \Closure) {
if (!$this->closureSerializer) {
$closureSerializer = $this->closureManager->getPreferredSerializer();
if (!$closureSerializer) {
throw new JsonSerializerException('Closure serializer not given. Unable to serialize closure.');
}
return [
static::CLOSURE_IDENTIFIER_KEY => true,
'value' => $this->closureSerializer->serialize($value)
'serializer' => $closureSerializer::class,
'value' => $closureSerializer->serialize($value)
];
}
return $this->serializeObject($value);
Expand Down Expand Up @@ -349,10 +367,12 @@ protected function unserializeData($value)
}

if (!empty($value[static::CLOSURE_IDENTIFIER_KEY])) {
if (!$this->closureSerializer) {
$serializerClass = isset($value['serializer']) ? $value['serializer'] : ClosureSerializer\SuperClosureSerializer::class;
$serializer = $this->closureManager->getSerializer($serializerClass);
if (!$serializer) {
throw new JsonSerializerException('Closure serializer not provided to unserialize closure');
}
return $this->closureSerializer->unserialize($value['value']);
return $serializer->unserialize($value['value']);
}

return array_map([$this, __FUNCTION__], $value);
Expand Down
26 changes: 26 additions & 0 deletions tests/ClosureSerializer/ClosureSerializerManagerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Zumba\JsonSerializer\Test\ClosureSerializer;

use PHPUnit\Framework\TestCase;
use Zumba\JsonSerializer\ClosureSerializer\ClosureSerializerManager;
use Zumba\JsonSerializer\ClosureSerializer\SuperClosureSerializer;

class ClosureSerializerManagerTest extends TestCase
{
public function testAddSerializer() {
$manager = new ClosureSerializerManager();
$this->assertEmpty($manager->getSerializer('foo'));
$manager->addSerializer(new SuperClosureSerializer(new \SuperClosure\Serializer()));
$this->assertNotEmpty($manager->getSerializer(SuperClosureSerializer::class));
}

public function testGetPreferredSerializer() {
$manager = new ClosureSerializerManager();
$this->assertNull($manager->getPreferredSerializer());

$serializer = new SuperClosureSerializer(new \SuperClosure\Serializer());
$manager->addSerializer($serializer);
$this->assertSame($serializer, $manager->getPreferredSerializer());
}
}
Loading

0 comments on commit 3d9f1b7

Please sign in to comment.