├── .github └── workflows │ └── tests.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── phpbench.json ├── phpunit.xml.dist ├── src ├── Hydration │ ├── Hydrate.php │ ├── HydrateUsingClosure.php │ └── HydrateUsingReflection.php ├── Reconstitution │ ├── Reconstitute.php │ ├── ReconstituteUsingInstantiatorAndHydrator.php │ └── Reconstitution.php └── Serialization │ ├── ArrayHelper.php │ ├── AutoSerializable.php │ └── RecursiveSerializer.php └── test ├── Hydrate ├── AbstractTestForHydration.php ├── Fixtures │ ├── ClassWithPrivateProperties.php │ └── ClassWithoutProperties.php ├── HydrateUsingClosureTest.php └── HydrateUsingReflectionTest.php ├── Performance ├── ReconstitutionBench.php ├── SerializableClassUsingTrait.php ├── SerializationBench.php ├── SomeOtherSerializableClass.php ├── SomeOtherSerializableClassUsingTrait.php └── TraditionalSerializableClass.php ├── Reconstitution ├── InstantiateAndHydrateTest.php └── ReconstitutionTest.php ├── Serialization ├── ArrayHelperTest.php ├── Fixtures │ ├── SerializableObjectUsingTrait.php │ ├── SerializableObjectWithNoCallbacks.php │ └── TraditionalSerializableObject.php ├── RecursiveSerializerTest.php └── SerializableTest.php └── bootstrap.php /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: null 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | tests: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | php-version: ['7.4', '8.0'] 15 | actions: 16 | - name: "Tests" 17 | run: vendor/bin/phpunit 18 | 19 | name: ${{ matrix.actions.name }} 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | 25 | # see https://github.com/shivammathur/setup-php 26 | - uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: ${{ matrix.php-version }} 29 | coverage: none 30 | 31 | # composer install cache - https://github.com/ramsey/composer-install 32 | - uses: "ramsey/composer-install@v1" 33 | 34 | - run: ${{ matrix.actions.run }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /phpunit.xml 3 | /composer.lock 4 | /build/ 5 | /test/SymfonyIntegration/cache/ 6 | /.phpunit.result.cache 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | - `v2.0.0` Upgrade to PHP 7.0 and some more modernization: 4 | - Use type-hints wherever possible. 5 | - Use `::class` constants wherever possible. 6 | - 7 | - `v1.2.0` Upgrade to Broadway 1.0: 8 | - Use `Broadway\Serializer\Serializable` instead of `Broadway\Serializer\SerializableInterface`. 9 | - Rename `BroadwaySerialization\Serialization\Serializable` to `BroadwaySerialization\Serialization\AutoSerializable` so the user doesn't need to alias the `Serializable` interface. 10 | 11 | - `v1.1.1` Start keeping a changelog. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017 Matthias Noback 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Broadway Serialization helper library 2 | 3 | By Matthias Noback 4 | 5 | Event-sourcing framework [Broadway](https://github.com/broadway/broadway) 6 | requires a lot of your objects (like domain events and often view models as 7 | well) to be serializable because they have to be stored in plain text and later 8 | be reconstituted. 9 | 10 | Making an object serializable requires you to write lots of pretty simple code. 11 | Even though that code is simple, you are likely to make mistakes in it. It's 12 | also a bit boring to write. To alleviate the pain, this library helps you to 13 | make objects serializable with in a few simple steps. 14 | 15 | ## Installation 16 | 17 | Just run 18 | 19 | composer require matthiasnoback/broadway-serialization 20 | 21 | ## Conventions 22 | 23 | This library is very simple and it *just works* because of a few simple and 24 | yet assumptions: 25 | 26 | 1. Serializable objects have at least one property. Properties can have any 27 | kind of scope. 28 | 2. Serializable objects implements Broadway's `Serializable` interface. 29 | 3. Properties contain scalar values, serializable objects, or arrays of 30 | serializable objects. 31 | 32 | ## Example 33 | 34 | ```php 35 | 36 | use Broadway\Serializer\Serializable; 37 | use BroadwaySerialization\Serialization\AutoSerializable; 38 | 39 | class SerializableObject implements SerializableInterface 40 | { 41 | use AutoSerializable; 42 | 43 | /** 44 | * @var string 45 | */ 46 | private $foo; 47 | 48 | /** 49 | * @var SerializableObject 50 | */ 51 | private $bar; 52 | 53 | /** 54 | * @var SerializableObject[] 55 | */ 56 | private $bars = []; 57 | 58 | protected static function deserializationCallbacks() 59 | { 60 | return [ 61 | 'bar' => ['SerializableObject', 'deserialize'], 62 | 'bars' => ['SerializableObject', 'deserialize'] 63 | ]; 64 | } 65 | } 66 | ``` 67 | 68 | By implementing `deserializationCallbacks()` you can define callables that 69 | should be called in order to deserialize the provided data. The callable will 70 | be called once for single values or multiple times for arrays of values. 71 | 72 | ## Setup 73 | 74 | The `Serializable` trait depends on a little bit of setup. Before calling its `deserialize()` method, make sure you 75 | have properly set up a `Reconstitute` service, like this: 76 | 77 | ```php 78 | use BroadwaySerialization\Reconstitution\Reconstitution; 79 | use BroadwaySerialization\Reconstitution\ReconstituteUsingInstantiatorAndHydrator; 80 | use Doctrine\Instantiator\Instantiator; 81 | use BroadwaySerialization\Hydration\HydrateUsingReflection; 82 | 83 | Reconstitution::reconstituteUsing( 84 | new ReconstituteUsingInstantiatorAndHydrator( 85 | new Instantiator(), 86 | new HydrateUsingReflection() 87 | ) 88 | ); 89 | ``` 90 | 91 | Note that this package ships with different implementations of the hydrator. Depending on your setup and preferences, you can also use the `HydrateUsingClosure` class. The former generally performs better than the `HydrateUsingReflection`. 92 | 93 | ## Performance 94 | 95 | When using this library, your personal performance will increase significantly. Of course, runtime performance will 96 | be worse (not noticeably though, unless you're actually deserializing millions of objects). 97 | 98 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matthiasnoback/broadway-serialization", 3 | "type": "library", 4 | "description": "Serialization helpers for Broadway", 5 | "keywords": ["Broadway", "serialization"], 6 | "homepage": "http://github.com/matthiasnoback/broadway-serialization", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Matthias Noback", 11 | "email": "matthiasnoback@gmail.com", 12 | "homepage": "http://php-and-symfony.matthiasnoback.nl" 13 | } 14 | ], 15 | "require": { 16 | "php": "^7.0|^8.0", 17 | "doctrine/instantiator": "^1.0", 18 | "broadway/broadway": "^2.1" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "BroadwaySerialization\\": "src/" 23 | } 24 | }, 25 | "autoload-dev": { 26 | "psr-4": { 27 | "BroadwaySerialization\\Test\\": "test/" 28 | } 29 | }, 30 | "require-dev": { 31 | "phpunit/phpunit": "^9.5", 32 | "phpbench/phpbench": "^1.0@alpha" 33 | }, 34 | "minimum-stability": "dev", 35 | "prefer-stable": true 36 | } 37 | -------------------------------------------------------------------------------- /phpbench.json: -------------------------------------------------------------------------------- 1 | { 2 | "bootstrap": "vendor/autoload.php", 3 | "path": "test/Performance" 4 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./test 10 | 11 | 12 | 13 | 14 | ./ 15 | 16 | ./test 17 | ./vendor 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Hydration/Hydrate.php: -------------------------------------------------------------------------------- 1 | hydrator = function (array $data, array $props) { 21 | foreach ($data as $key => $value) { 22 | if (isset($props[$key])) { 23 | $this->{$key} = $value; 24 | } 25 | } 26 | }; 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function hydrate(array $data, $object) 33 | { 34 | $class = get_class($object); 35 | if (!isset($this->cache[$class])) { 36 | $this->getProps($class); 37 | } 38 | 39 | $this->hydrator->call($object, $data, $this->cache[$class]); 40 | } 41 | 42 | /** 43 | * @param $class 44 | */ 45 | private function getProps($class) 46 | { 47 | $this->cache[$class] = []; 48 | foreach ((new \ReflectionClass($class))->getProperties() as $property) { 49 | $this->cache[$class][$property->getName()] = true; 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /src/Hydration/HydrateUsingReflection.php: -------------------------------------------------------------------------------- 1 | propertiesOf($object) as $name => $property) { 22 | if (!isset($data[$name])) { 23 | continue; 24 | } 25 | 26 | $property->setValue($object, $data[$name]); 27 | } 28 | } 29 | 30 | /** 31 | * @param object $object 32 | * @return \ReflectionProperty[] 33 | */ 34 | private function propertiesOf($object): array 35 | { 36 | $className = get_class($object); 37 | 38 | if (!isset($this->properties[$className])) { 39 | $this->properties[$className] = []; 40 | foreach ((new \ReflectionObject($object))->getProperties() as $property) { 41 | /** @var \ReflectionProperty $property */ 42 | $property->setAccessible(true); 43 | $this->properties[$className][$property->getName()] = $property; 44 | } 45 | } 46 | 47 | return $this->properties[$className]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Reconstitution/Reconstitute.php: -------------------------------------------------------------------------------- 1 | instantiator = $instantiator; 31 | $this->hydrator = $hydrator; 32 | } 33 | 34 | /** 35 | * @inheritdoc 36 | */ 37 | public function objectFrom(string $className, array $data) 38 | { 39 | $object = $this->instantiator->instantiate($className); 40 | 41 | $this->hydrator->hydrate($data, $object); 42 | 43 | return $object; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Reconstitution/Reconstitution.php: -------------------------------------------------------------------------------- 1 | objectFrom( 24 | get_called_class(), 25 | RecursiveSerializer::deserialize($data, self::deserializationCallbacks()) 26 | ); 27 | } 28 | 29 | /** 30 | * @see \Broadway\Serializer\Serializable::serialize() 31 | * 32 | * @return array Values of properties that should be serialized 33 | */ 34 | final public function serialize(): array 35 | { 36 | return RecursiveSerializer::serialize(get_object_vars($this)); 37 | } 38 | 39 | /** 40 | * Override this function if specific properties contain objects that need to be deserialized as well. Return an 41 | * array of which each key corresponds to an existing property and each value is a callable which handles the 42 | * deserialization: 43 | * 44 | * [ 45 | * 'property' => [DesiredClass::class, 'deserialize'] 46 | * ] 47 | * 48 | * @return array 49 | */ 50 | protected static function deserializationCallbacks(): array 51 | { 52 | return []; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Serialization/RecursiveSerializer.php: -------------------------------------------------------------------------------- 1 | $value) { 26 | if (is_array($value)) { 27 | $value = self::serialize($value); 28 | } 29 | 30 | if ($value instanceof Serializable) { 31 | $value = $value->serialize(); 32 | } 33 | 34 | $serializedData[$property] = $value; 35 | } 36 | 37 | return $serializedData; 38 | } 39 | 40 | /** 41 | * Recursively deserialize an array of (property) values. Use callbacks to further deserialize more complicated 42 | * values. 43 | * 44 | * @param array $values 45 | * @param array $callbacks 46 | * @return array 47 | */ 48 | public static function deserialize(array $values, array $callbacks = []): array 49 | { 50 | $deserializedData = []; 51 | foreach ($values as $property => $value) { 52 | if (isset($callbacks[$property])) { 53 | if (is_array($value) && ArrayHelper::isNumericallyIndexed($value)) { 54 | $value = array_map($callbacks[$property], $value); 55 | } elseif ($value !== null) { 56 | // if $value is null, we don't call the callable, since its type-hint may then cause a fatal error 57 | $value = $callbacks[$property]($value); 58 | } 59 | } 60 | 61 | $deserializedData[$property] = $value; 62 | } 63 | 64 | return $deserializedData; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/Hydrate/AbstractTestForHydration.php: -------------------------------------------------------------------------------- 1 | getHydrator(); 17 | $object = new ClassWithPrivateProperties(); 18 | $hydrate->hydrate(['foo' => 'bar'], $object); 19 | 20 | $this->assertSame('bar', $object->foo()); 21 | } 22 | 23 | /** 24 | * @test 25 | */ 26 | public function it_ignores_keys_that_are_not_defined_in_the_data_array() 27 | { 28 | $hydrate = $this->getHydrator(); 29 | $object = new ClassWithPrivateProperties(); 30 | 31 | // 'foo' is not defined, which should be no problem 32 | $hydrate->hydrate([], $object); 33 | 34 | $this->assertSame(null, $object->foo()); 35 | } 36 | 37 | /** 38 | * @test 39 | */ 40 | public function it_ignores_extra_keys_in_the_data_array() 41 | { 42 | $hydrate = $this->getHydrator(); 43 | $object = new ClassWithPrivateProperties(); 44 | 45 | // 'extra' is not a property, which should be no problem 46 | $hydrate->hydrate(['extra' => 'no problem', 'foo' => 'bar'], $object); 47 | 48 | $this->assertSame('bar', $object->foo()); 49 | } 50 | 51 | /** 52 | * @test 53 | */ 54 | public function it_works_for_objects_without_properties() 55 | { 56 | $hydrate = $this->getHydrator(); 57 | $object = new ClassWithoutProperties(); 58 | 59 | // This class doesn't have any property which should be no problem 60 | $hydrate->hydrate([], $object); 61 | 62 | $this->assertTrue(true); // to prevent a strict warning 63 | } 64 | 65 | /** 66 | * @return Hydrate 67 | */ 68 | abstract protected function getHydrator(); 69 | } -------------------------------------------------------------------------------- /test/Hydrate/Fixtures/ClassWithPrivateProperties.php: -------------------------------------------------------------------------------- 1 | foo; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/Hydrate/Fixtures/ClassWithoutProperties.php: -------------------------------------------------------------------------------- 1 | 'foo', 20 | 'integerProperty' => 20, 21 | 'nullProperty' => null, 22 | 'arrayProperty' => ['foo' => 'bar', 'bar' => 'baz'], 23 | 'objectProperty' => [ 24 | 'foo' => 'foo', 25 | ], 26 | 'objectsProperty' => [ 27 | [ 28 | 'foo' => 'bar', 29 | ], 30 | [ 31 | 'foo' => 'baz', 32 | ], 33 | ] 34 | ]; 35 | 36 | 37 | public function setupReconstituteUsingInstantiatorAndReflection() 38 | { 39 | $reconstitute = new ReconstituteUsingInstantiatorAndHydrator(new Instantiator(), new HydrateUsingReflection()); 40 | Reconstitution::reconstituteUsing($reconstitute); 41 | } 42 | 43 | public function setupReconstituteUsingInstantiatorAndClosure() 44 | { 45 | $reconstitute = new ReconstituteUsingInstantiatorAndHydrator(new Instantiator(), new HydrateUsingClosure()); 46 | Reconstitution::reconstituteUsing($reconstitute); 47 | } 48 | 49 | /** 50 | * @Warmup(10) 51 | * @Revs(1000) 52 | * @Groups({"traditional"}) 53 | */ 54 | public function benchDeserializeTraditionalObject() 55 | { 56 | TraditionalSerializableClass::deserialize($this->deserializationData); 57 | } 58 | 59 | /** 60 | * @Warmup(10) 61 | * @Revs(1000) 62 | * @Groups({"trait"}) 63 | * @BeforeMethods({"setupReconstituteUsingInstantiatorAndReflection"}) 64 | */ 65 | public function benchDeserializeObjectUsingReflection() 66 | { 67 | SerializableClassUsingTrait::deserialize($this->deserializationData); 68 | } 69 | 70 | /** 71 | * @Warmup(10) 72 | * @Revs(1000) 73 | * @Groups({"trait"}) 74 | * @BeforeMethods({"setupReconstituteUsingInstantiatorAndClosure"}) 75 | */ 76 | public function benchDeserializeObjectUsingClosure() 77 | { 78 | SerializableClassUsingTrait::deserialize($this->deserializationData); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /test/Performance/SerializableClassUsingTrait.php: -------------------------------------------------------------------------------- 1 | 'bar', 'bar' => 'baz']; 17 | private $objectProperty; 18 | private $objectsProperty; 19 | 20 | public function __construct() 21 | { 22 | $this->objectProperty = new SomeOtherSerializableClassUsingTrait('foo'); 23 | $this->objectsProperty = [ 24 | new SomeOtherSerializableClassUsingTrait('bar'), 25 | new SomeOtherSerializableClassUsingTrait('baz'), 26 | ]; 27 | } 28 | 29 | protected static function deserializationCallbacks(): array 30 | { 31 | return [ 32 | 'objectProperty' => [ 33 | SomeOtherSerializableClassUsingTrait::class, 34 | 'deserialize' 35 | ], 36 | 'objectsProperty' => [ 37 | SomeOtherSerializableClassUsingTrait::class, 38 | 'deserialize' 39 | ], 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/Performance/SerializationBench.php: -------------------------------------------------------------------------------- 1 | traditionalSerializableClass = new TraditionalSerializableClass(); 27 | $this->serializableClassUsingTrait = new SerializableClassUsingTrait(); 28 | } 29 | 30 | /** 31 | * @Warmup(10) 32 | * @Revs(1000) 33 | * @Groups({"traditional"}) 34 | * @BeforeMethods({"setup"}) 35 | */ 36 | public function benchSerializeObjectWithOnlyScalarProperties() 37 | { 38 | $this->traditionalSerializableClass->serialize(); 39 | } 40 | 41 | /** 42 | * @Warmup(10) 43 | * @Revs(1000) 44 | * @Groups({"trait"}) 45 | * @BeforeMethods({"setup"}) 46 | */ 47 | public function benchSerializeObjectWithOnlyScalarPropertiesWithTrait() 48 | { 49 | $this->serializableClassUsingTrait->serialize(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /test/Performance/SomeOtherSerializableClass.php: -------------------------------------------------------------------------------- 1 | $this->foo 21 | ]; 22 | } 23 | 24 | public function __construct($foo) 25 | { 26 | $this->foo = $foo; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/Performance/SomeOtherSerializableClassUsingTrait.php: -------------------------------------------------------------------------------- 1 | foo = $foo; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Performance/TraditionalSerializableClass.php: -------------------------------------------------------------------------------- 1 | 'bar', 'bar' => 'baz']; 14 | private $objectProperty; 15 | private $objectsProperty; 16 | 17 | public function __construct() 18 | { 19 | $this->objectProperty = new SomeOtherSerializableClass('foo'); 20 | $this->objectsProperty = [ 21 | new SomeOtherSerializableClass('bar'), 22 | new SomeOtherSerializableClass('baz'), 23 | ]; 24 | } 25 | 26 | public static function deserialize(array $data) 27 | { 28 | $object = new self(); 29 | 30 | $object->stringProperty = $data['stringProperty']; 31 | $object->integerProperty = $data['integerProperty']; 32 | $object->nullProperty = $data['nullProperty']; 33 | $object->arrayProperty = $data['arrayProperty']; 34 | $object->objectProperty = is_array($data['objectProperty']) ? SomeOtherSerializableClass::deserialize($data['objectProperty']) : null; 35 | $object->objectsProperty = array_map( 36 | function ($data) { 37 | return SomeOtherSerializableClass::deserialize($data); 38 | }, 39 | $data['objectsProperty'] 40 | ); 41 | 42 | return $object; 43 | } 44 | 45 | public function serialize(): array 46 | { 47 | return [ 48 | 'stringProperty' => $this->stringProperty, 49 | 'integerProperty' => $this->integerProperty, 50 | 'nullProperty' => $this->nullProperty, 51 | 'arrayProperty' => $this->arrayProperty, 52 | 'objectProperty' => $this->objectProperty->serialize(), 53 | 'objectsProperty' => array_map(function (SomeOtherSerializableClass $object) { 54 | return $object->serialize(); 55 | }, $this->objectsProperty) 56 | ]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/Reconstitution/InstantiateAndHydrateTest.php: -------------------------------------------------------------------------------- 1 | 'baz']; 23 | $object = $reconstitute->objectFrom($className, $data); 24 | 25 | $this->assertInstanceOf($className, $object); 26 | $this->assertSame($data['bar'], $object->getBar()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/Reconstitution/ReconstitutionTest.php: -------------------------------------------------------------------------------- 1 | expectException(LogicException::class); 19 | 20 | Reconstitution::reconstitute(); 21 | } 22 | 23 | /** 24 | * @test 25 | */ 26 | public function it_returns_the_previously_provided_reconstitute_object() 27 | { 28 | $reconstitute = $this->dummyReconstitute(); 29 | Reconstitution::reconstituteUsing($reconstitute); 30 | 31 | $this->assertSame($reconstitute, Reconstitution::reconstitute()); 32 | } 33 | 34 | private function dummyReconstitute() 35 | { 36 | return $this->createMock(Reconstitute::class); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/Serialization/ArrayHelperTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(ArrayHelper::isNumericallyIndexed([])); 17 | } 18 | 19 | /** 20 | * @test 21 | */ 22 | public function an_array_with_an_integer_key_is_numerically_indexed() 23 | { 24 | $this->assertTrue(ArrayHelper::isNumericallyIndexed([0 => 'first value'])); 25 | } 26 | 27 | /** 28 | * @test 29 | */ 30 | public function an_array_with_a_non_integer_key_is_not_numerically_indexed() 31 | { 32 | $this->assertFalse(ArrayHelper::isNumericallyIndexed(['first_key' => 'first value'])); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/Serialization/Fixtures/SerializableObjectUsingTrait.php: -------------------------------------------------------------------------------- 1 | foo = $foo; 20 | $this->bar = $bar; 21 | $this->bars = $bars; 22 | } 23 | 24 | protected static function deserializationCallbacks(): array 25 | { 26 | return [ 27 | 'bar' => [TraditionalSerializableObject::class, 'deserialize'], 28 | 'bars' => [TraditionalSerializableObject::class, 'deserialize'] 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/Serialization/Fixtures/SerializableObjectWithNoCallbacks.php: -------------------------------------------------------------------------------- 1 | bar = $bar; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/Serialization/Fixtures/TraditionalSerializableObject.php: -------------------------------------------------------------------------------- 1 | bar = $value; 15 | } 16 | 17 | public function getBar() 18 | { 19 | return $this->bar; 20 | } 21 | 22 | public static function deserialize(array $data) 23 | { 24 | return new self($data['bar']); 25 | } 26 | 27 | public function serialize(): array 28 | { 29 | return ['bar' => $this->bar]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/Serialization/RecursiveSerializerTest.php: -------------------------------------------------------------------------------- 1 | 'bar', 19 | 'bar' => new TraditionalSerializableObject('bing'), 20 | 'baz' => [ 21 | new TraditionalSerializableObject('bang'), 22 | new TraditionalSerializableObject('bong') 23 | ] 24 | ]); 25 | 26 | $this->assertEquals( 27 | [ 28 | 'foo' => 'bar', 29 | 'bar' => ['bar' => 'bing'], 30 | 'baz' => [ 31 | ['bar' => 'bang'], 32 | ['bar' => 'bong'] 33 | ] 34 | ], 35 | $serializedData 36 | ); 37 | } 38 | 39 | /** 40 | * @test 41 | */ 42 | public function it_deserializes_data_recursively() 43 | { 44 | $serializedData = [ 45 | 'foo' => 'bar', 46 | 'bar' => ['bar' => 'bing'], 47 | 'baz' => [ 48 | ['bar' => 'bang'], 49 | ['bar' => 'bong'] 50 | ] 51 | ]; 52 | 53 | $this->assertEquals( 54 | [ 55 | 'foo' => 'bar', 56 | 'bar' => new TraditionalSerializableObject('bing'), 57 | 'baz' => [ 58 | new TraditionalSerializableObject('bang'), 59 | new TraditionalSerializableObject('bong') 60 | ] 61 | ], 62 | RecursiveSerializer::deserialize($serializedData, [ 63 | 'bar' => function(array $data) { 64 | return new TraditionalSerializableObject($data['bar']); 65 | }, 66 | 'baz' => function(array $data) { 67 | return new TraditionalSerializableObject($data['bar']); 68 | } 69 | ]) 70 | ); 71 | } 72 | 73 | /** 74 | * @test 75 | */ 76 | public function when_serializing_it_leaves_an_array_of_scalar_values_as_it_is() 77 | { 78 | $serializedData = [ 79 | 'foo' => ['bar', 'baz'] 80 | ]; 81 | 82 | $this->assertSame($serializedData, RecursiveSerializer::serialize($serializedData)); 83 | } 84 | 85 | /** 86 | * @test 87 | */ 88 | public function when_deserializing_it_leaves_an_array_of_scalar_values_as_it_is() 89 | { 90 | $serializedData = [ 91 | 'foo' => ['bar', 'baz'] 92 | ]; 93 | 94 | $this->assertSame($serializedData, RecursiveSerializer::deserialize($serializedData)); 95 | } 96 | 97 | /** 98 | * @test 99 | */ 100 | public function when_deserializing_do_not_call_the_callable_when_the_value_is_null() 101 | { 102 | $serializedData = [ 103 | 'foo' => null 104 | ]; 105 | $callables = [ 106 | 'foo' => function(array $data) { 107 | // when passing null, this would cause a fatal error 108 | } 109 | ]; 110 | 111 | $this->assertSame($serializedData, RecursiveSerializer::deserialize($serializedData, $callables)); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /test/Serialization/SerializableTest.php: -------------------------------------------------------------------------------- 1 | serialize(); 42 | $reconstitutedInstance = SerializableObjectUsingTrait::deserialize($data); 43 | 44 | $this->assertEquals($originalObject, $reconstitutedInstance); 45 | } 46 | 47 | /** 48 | * @test 49 | */ 50 | public function it_has_no_custom_callbacks_by_default() 51 | { 52 | $originalObject = new SerializableObjectWithNoCallbacks('baz'); 53 | 54 | $data = $originalObject->serialize(); 55 | $reconstitutedInstance = SerializableObjectWithNoCallbacks::deserialize($data); 56 | 57 | $this->assertEquals($originalObject, $reconstitutedInstance); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/bootstrap.php: -------------------------------------------------------------------------------- 1 |