diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..75aefc8 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +# for php-coveralls +service_name: travis-ci +coverage_clover: build/logs/clover.xml diff --git a/.gitignore b/.gitignore index 688d850..c7c729e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea +.php_cs.cache composer.lock -vendor \ No newline at end of file +vendor diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..209b70c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,43 @@ +language: php + +matrix: + fast_finish: true + include: + - php: 7.4 + env: + - DEPENDENCIES="" + - EXECUTE_CS_CHECK=true + - TEST_COVERAGE=true + - php: 8.0 + env: + - DEPENDENCIES="" + - EXECUTE_CS_CHECK=true + - TEST_COVERAGE=true + +cache: + directories: + - $HOME/.composer/cache + - $HOME/.php-cs-fixer + - $HOME/.local + +before_script: + - mkdir -p "$HOME/.php-cs-fixer" + - phpenv config-rm xdebug.ini + - composer self-update + - composer update --prefer-source $DEPENDENCIES + +script: + - if [[ $TEST_COVERAGE == 'true' ]]; then php -dzend_extension=xdebug.so ./vendor/bin/phpunit --coverage-text --coverage-clover ./build/logs/clover.xml; else ./vendor/bin/phpunit; fi + - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/php-cs-fixer fix src -v --diff --dry-run; fi + - if [[ $EXECUTE_CS_CHECK == 'true' ]]; then ./vendor/bin/docheader check examples/ src/ tests/; fi + +after_success: + - if [[ $TEST_COVERAGE == 'true' ]]; then php ./vendor/bin/php-coveralls -v; fi + +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/61c75218816eebde4486 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always diff --git a/README.md b/README.md index 3bc38af..6d4efe0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # EventEngine\Data +[![Build Status](https://travis-ci.com/event-engine/php-data.svg?branch=master)](https://travis-ci.com/event-engine/php-data) +[![Coverage Status](https://coveralls.io/repos/github/event-engine/php-data/badge.svg?branch=master)](https://coveralls.io/github/event-engine/php-data?branch=master) + Generate Immutable Objects with ease! ![Value Object Template vo_string](https://event-engine.io/api/img/vo_string.gif) diff --git a/composer.json b/composer.json index 9f955e1..be3fa40 100644 --- a/composer.json +++ b/composer.json @@ -16,14 +16,14 @@ } ], "require": { - "php": "^7.4", - "roave/security-advisories": "dev-master" + "php": "^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^7.0", - "prooph/php-cs-fixer-config": "^0.3", - "satooshi/php-coveralls": "^1.0", - "malukenho/docheader": "^0.1.4" + "phpunit/phpunit": "^8.0 || ^9.0", + "prooph/php-cs-fixer-config": "^0.4", + "php-coveralls/php-coveralls": "^2.2", + "malukenho/docheader": "^0.1.4", + "roave/security-advisories": "dev-latest" }, "autoload": { "psr-4": { @@ -43,8 +43,8 @@ "@test" ], "docheader": "vendor/bin/docheader check src/ tests/", - "cs": "php-cs-fixer fix -v --diff --dry-run", - "cs-fix": "php-cs-fixer fix -v --diff", + "cs": "php-cs-fixer fix src -v --diff --dry-run", + "cs-fix": "php-cs-fixer fix src -v --diff", "test": "vendor/bin/phpunit" } } diff --git a/src/DataConverter.php b/src/DataConverter.php index f9f3194..7ef87ba 100644 --- a/src/DataConverter.php +++ b/src/DataConverter.php @@ -1,7 +1,7 @@ + * (c) 2018-2021 prooph software GmbH * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. diff --git a/src/ImmutableRecord.php b/src/ImmutableRecord.php index 9aae1c6..a705147 100644 --- a/src/ImmutableRecord.php +++ b/src/ImmutableRecord.php @@ -1,7 +1,7 @@ + * (c) 2018-2021 prooph software GmbH * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -11,6 +11,12 @@ namespace EventEngine\Data; +/** + * Interface ImmutableRecord + * + * @package EventEngine\Data + * @psalm-immutable + */ interface ImmutableRecord { const PHP_TYPE_STRING = 'string'; diff --git a/src/ImmutableRecordDataConverter.php b/src/ImmutableRecordDataConverter.php index adb3165..5029cde 100644 --- a/src/ImmutableRecordDataConverter.php +++ b/src/ImmutableRecordDataConverter.php @@ -1,7 +1,7 @@ + * (c) 2018-2021 prooph software GmbH * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -37,7 +37,7 @@ public function canConvertTypeToData(string $type): bool { $class = $this->getClassOfType($type); - if(!class_exists($class)) { + if (!class_exists($class)) { return false; } diff --git a/src/ImmutableRecordLogic.php b/src/ImmutableRecordLogic.php index 615219d..5516f05 100644 --- a/src/ImmutableRecordLogic.php +++ b/src/ImmutableRecordLogic.php @@ -1,7 +1,7 @@ + * (c) 2018-2021 prooph software GmbH * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. @@ -11,6 +11,12 @@ namespace EventEngine\Data; +/** + * Trait ImmutableRecordLogic + * @package EventEngine\Data + * + * @psalm-immutable + */ trait ImmutableRecordLogic { /** @@ -36,7 +42,7 @@ private function init(): void * @param array $recordData * @return self */ - public static function fromRecordData(array $recordData) + public static function fromRecordData(array $recordData): self { return new self($recordData); } @@ -45,12 +51,12 @@ public static function fromRecordData(array $recordData) * @param array $nativeData * @return self */ - public static function fromArray(array $nativeData) + public static function fromArray(array $nativeData): self { return new self(null, $nativeData); } - private function __construct(array $recordData = null, array $nativeData = null) + private function __construct(array|null $recordData = null, array|null $nativeData = null) { if (null === self::$__propTypeMap) { self::$__propTypeMap = self::buildPropTypeMap(); @@ -73,7 +79,7 @@ private function __construct(array $recordData = null, array $nativeData = null) * @param array $recordData * @return self */ - public function with(array $recordData) + public function with(array $recordData): self { $copy = clone $this; $copy->setRecordData($recordData); @@ -87,6 +93,12 @@ public function toArray(): array $arrayPropItemTypeMap = self::getArrayPropItemTypeMapFromMethodOrCache(); foreach (self::$__propTypeMap as $key => [$type, $isNative, $isNullable]) { + $specialKey = $key; + + if ($this instanceof SpecialKeySupport) { + $specialKey = $this->convertKeyForArray($key); + } + switch ($type) { case ImmutableRecord::PHP_TYPE_STRING: case ImmutableRecord::PHP_TYPE_INT: @@ -94,24 +106,24 @@ public function toArray(): array case ImmutableRecord::PHP_TYPE_BOOL: case ImmutableRecord::PHP_TYPE_ARRAY: if (\array_key_exists($key, $arrayPropItemTypeMap) && ! self::isScalarType($arrayPropItemTypeMap[$key])) { - if ($isNullable && $this->{$key}() === null) { - $nativeData[$key] = null; + if ($isNullable && $this->{$key} === null) { + $nativeData[$specialKey] = null; continue 2; } - $nativeData[$key] = \array_map(function ($item) use ($key, &$arrayPropItemTypeMap) { + $nativeData[$specialKey] = \array_map(function ($item) use ($key, &$arrayPropItemTypeMap) { return $this->voTypeToNative($item, $key, $arrayPropItemTypeMap[$key]); - }, $this->{$key}()); + }, $this->{$key}); } else { - $nativeData[$key] = $this->{$key}(); + $nativeData[$specialKey] = $this->{$key}; } break; default: - if ($isNullable && $this->{$key}() === null) { - $nativeData[$key] = null; + if ($isNullable && (! isset($this->{$key}))) { + $nativeData[$specialKey] = null; continue 2; } - $nativeData[$key] = $this->voTypeToNative($this->{$key}(), $key, $type); + $nativeData[$specialKey] = $this->voTypeToNative($this->{$key}, $key, $type); } } @@ -120,38 +132,50 @@ public function toArray(): array public function equals(ImmutableRecord $other): bool { - if(get_class($this) !== get_class($other)) { + if (\get_class($this) !== \get_class($other)) { return false; } return $this->toArray() === $other->toArray(); } - private function setRecordData(array $recordData) + private function setRecordData(array $recordData): void { foreach ($recordData as $key => $value) { - $this->assertType($key, $value); - $this->{$key} = $value; + $specialKey = $key; + + if ($this instanceof SpecialKeySupport) { + $specialKey = $this->convertKeyForRecord($key); + } + + $this->assertType($specialKey, $value); + $this->{$specialKey} = $value; } } - private function setNativeData(array $nativeData) + private function setNativeData(array $nativeData): void { $recordData = []; $arrayPropItemTypeMap = self::getArrayPropItemTypeMapFromMethodOrCache(); foreach ($nativeData as $key => $val) { - if (! isset(self::$__propTypeMap[$key])) { + $specialKey = $key; + + if ($this instanceof SpecialKeySupport) { + $specialKey = $this->convertKeyForRecord($key); + } + + if (! isset(self::$__propTypeMap[$specialKey])) { throw new \InvalidArgumentException(\sprintf( - 'Invalid property passed to Record %s. Got property with key ' . $key, + 'Invalid property passed to Record %s. Got property with key ' . $specialKey, \get_called_class() )); } - [$type, $isNative, $isNullable] = self::$__propTypeMap[$key]; + [$type, $isNative, $isNullable] = self::$__propTypeMap[$specialKey]; if ($val === null) { if (! $isNullable) { - throw new \RuntimeException("Got null for non nullable property $key of Record " . \get_called_class()); + throw new \RuntimeException("Got null for non nullable property $specialKey of Record " . \get_called_class()); } $recordData[$key] = null; @@ -166,9 +190,9 @@ private function setNativeData(array $nativeData) $recordData[$key] = $val; break; case ImmutableRecord::PHP_TYPE_ARRAY: - if (\array_key_exists($key, $arrayPropItemTypeMap) && ! self::isScalarType($arrayPropItemTypeMap[$key])) { - $recordData[$key] = \array_map(function ($item) use ($key, &$arrayPropItemTypeMap) { - return $this->fromType($item, $arrayPropItemTypeMap[$key]); + if (\array_key_exists($specialKey, $arrayPropItemTypeMap) && ! self::isScalarType($arrayPropItemTypeMap[$specialKey])) { + $recordData[$key] = \array_map(function ($item) use ($specialKey, &$arrayPropItemTypeMap) { + return $this->fromType($item, $arrayPropItemTypeMap[$specialKey]); }, $val); } else { $recordData[$key] = $val; @@ -182,10 +206,10 @@ private function setNativeData(array $nativeData) $this->setRecordData($recordData); } - private function assertAllNotNull() + private function assertAllNotNull(): void { foreach (self::$__propTypeMap as $key => [$type, $isNative, $isNullable]) { - if (null === $this->{$key} && ! $isNullable) { + if (! isset($this->{$key}) && ! $isNullable) { throw new \InvalidArgumentException(\sprintf( 'Missing record data for key %s of record %s.', $key, @@ -195,7 +219,7 @@ private function assertAllNotNull() } } - private function assertType(string $key, $value) + private function assertType(string $key, $value): void { if (! isset(self::$__propTypeMap[$key])) { throw new \InvalidArgumentException(\sprintf( @@ -264,7 +288,7 @@ private function isType(string $type, string $key, $value): bool } } - private static function buildPropTypeMap() + private static function buildPropTypeMap(): array { $refObj = new \ReflectionClass(__CLASS__); @@ -383,12 +407,12 @@ private static function getArrayPropItemTypeMapFromMethodOrCache(): array } /** - * @var array + * @var array|null */ private static $__propTypeMap; /** - * @var array + * @var array|null */ private static $__arrayPropItemTypeMap; } diff --git a/src/SpecialKeySupport.php b/src/SpecialKeySupport.php new file mode 100644 index 0000000..158c0d0 --- /dev/null +++ b/src/SpecialKeySupport.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace EventEngine\Data; + +/** + * Implement this interface in your ImmutableRecord class if you have special keys like snake_case. + */ +interface SpecialKeySupport +{ + /** + * Converts the given key to key name for the record. For example if the key is first_name and you have a class + * property firstName then you have to convert it to camel case. + * + * @param string $key + * @return string + */ + public function convertKeyForRecord(string $key): string; + + /** + * Converts the given key to key name for the array data. For example if you have a class property firstName + * and want to have snake case array keys then you have to convert it to first_name. + * + * @param string $key + * @return string + */ + public function convertKeyForArray(string $key): string; +} diff --git a/tests/ImmutableRecordDataConverterTest.php b/tests/ImmutableRecordDataConverterTest.php new file mode 100644 index 0000000..77c6e5c --- /dev/null +++ b/tests/ImmutableRecordDataConverterTest.php @@ -0,0 +1,121 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data; + +use EventEngine\Data\ImmutableRecordDataConverter; +use EventEngineTest\Data\Stub\ImmutableItem; +use EventEngineTest\Data\Stub\TypeHintedImmutableRecordWithValueObjects; +use PHPUnit\Framework\TestCase; +use stdClass; + +final class ImmutableRecordDataConverterTest extends TestCase +{ + private $data = []; + + protected function setUp(): void + { + parent::setUp(); + + $this->data = [ + 'name' => 'test', + 'version' => 1, + 'itemList' => [['name' => 'one']], + 'access' => true, + ]; + } + + /** + * @test + */ + public function it_converts_immutable_record_to_array() + { + $valueObjects = TypeHintedImmutableRecordWithValueObjects::fromArray($this->data); + + $dataConverter = new ImmutableRecordDataConverter(); + + $this->data['type'] = null; + $this->data['percentage'] = 0.5; + + + $this->assertEquals( + $this->data, + $dataConverter->convertDataToArray( + TypeHintedImmutableRecordWithValueObjects::class, + $valueObjects + ) + ); + } + + /** + * @test + */ + public function it_returns_array_if_passed_as_input() + { + $input = ["a" => "test"]; + + $dataConverter = new ImmutableRecordDataConverter(); + + $output = $dataConverter->convertDataToArray('data', $input); + + $this->assertEquals($input, $output); + } + + /** + * @test + */ + public function it_returns_false_if_unknown_class_is_passed() + { + $dataConverter = new ImmutableRecordDataConverter(); + + $this->assertFalse($dataConverter->canConvertTypeToData(ImmutableItem::class . 'Unknown')); + } + + /** + * @test + */ + public function it_converts_stdClass_to_array() + { + $obj = new stdClass(); + + $obj->test = "This is a test"; + $obj->msg = "With a message"; + + $this->assertEquals([ + 'test' => "This is a test", + 'msg' => "With a message", + ], + (new ImmutableRecordDataConverter())->convertDataToArray(stdClass::class, $obj) + ); + } + + /** + * @test + */ + public function it_converts_array_to_immutable_record() + { + $dataConverter = new ImmutableRecordDataConverter(); + + $this->assertTrue($dataConverter->canConvertTypeToData(TypeHintedImmutableRecordWithValueObjects::class)); + + $valueObjects = $dataConverter->convertArrayToData( + TypeHintedImmutableRecordWithValueObjects::class, + $this->data + ); + + $this->data['type'] = null; + $this->data['percentage'] = 0.5; + + $this->assertEquals( + $this->data, + $valueObjects->toArray() + ); + } +} diff --git a/tests/ImmutableRecordLogicTest.php b/tests/ImmutableRecordLogicTest.php index 19a7515..e8f9ab5 100644 --- a/tests/ImmutableRecordLogicTest.php +++ b/tests/ImmutableRecordLogicTest.php @@ -1,25 +1,44 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ declare(strict_types=1); namespace EventEngineTest\Data; +use EventEngineTest\Data\Stub\ImmutableItem; use EventEngineTest\Data\Stub\ImmutableRecordWithNoTypes; use EventEngineTest\Data\Stub\ImmutableRecordWithTypedGetters; +use EventEngineTest\Data\Stub\RecordWithSpecialKey; +use EventEngineTest\Data\Stub\RecordWithStringList; use EventEngineTest\Data\Stub\TypeHintedImmutableRecord; +use EventEngineTest\Data\Stub\TypeHintedImmutableRecordWithValueObjects; +use EventEngineTest\Data\Stub\ValueObject\Access; +use EventEngineTest\Data\Stub\ValueObject\ItemList; +use EventEngineTest\Data\Stub\ValueObject\Name; +use EventEngineTest\Data\Stub\ValueObject\Percentage; +use EventEngineTest\Data\Stub\ValueObject\Type; +use EventEngineTest\Data\Stub\ValueObject\Version; use PHPUnit\Framework\TestCase; final class ImmutableRecordLogicTest extends TestCase { private $data = []; - protected function setUp() + protected function setUp(): void { parent::setUp(); $this->data = [ 'name' => 'test', 'version' => 1, + 'itemList' => [['name' => 'one']], + 'access' => true, ]; } @@ -31,6 +50,7 @@ public function it_detects_type_hinted_properties() $typeHinted = TypeHintedImmutableRecord::fromArray($this->data); $this->data['type'] = null; + $this->data['percentage'] = 0.5; $this->assertEquals( $this->data, @@ -46,6 +66,7 @@ public function it_detects_coupled_getters_for_properties() $typedGetters = ImmutableRecordWithTypedGetters::fromArray($this->data); $this->data['type'] = null; + $this->data['percentage'] = 0.5; $this->assertEquals( $this->data, @@ -53,6 +74,181 @@ public function it_detects_coupled_getters_for_properties() ); } + /** + * @test + */ + public function it_can_handle_value_objects() + { + $valueObjects = TypeHintedImmutableRecordWithValueObjects::fromArray($this->data); + + $this->data['type'] = null; + $this->data['percentage'] = 0.5; + + $this->assertEquals( + $this->data, + $valueObjects->toArray() + ); + } + + /** + * @test + */ + public function it_takes_value_object_as_initialization_params() + { + $valueObjects = TypeHintedImmutableRecordWithValueObjects::fromRecordData([ + 'name' => Name::fromString($this->data['name']), + 'type' => Type::fromString('value_object'), + 'version' => Version::fromInt($this->data['version']), + 'access' => Access::fromBool($this->data['access']), + 'percentage' => Percentage::fromFloat(0.9), + 'itemList' => ItemList::fromItems(ImmutableItem::fromRecordData(['name' => 'one'])), + ]); + + $this->data['type'] = 'value_object'; + $this->data['percentage'] = 0.9; + + $this->assertEquals( + $this->data, + $valueObjects->toArray() + ); + } + + /** + * @test + */ + public function it_returns_new_record_with_changed_properties() + { + $valueObjects = TypeHintedImmutableRecordWithValueObjects::fromArray($this->data); + + $changedValueObjects = $valueObjects->with([ + 'version' => Version::fromInt(2), + 'percentage' => Percentage::fromFloat(0.9), + ]); + + $this->data['type'] = null; + $this->data['percentage'] = 0.5; + + $this->assertEquals( + $this->data, + $valueObjects->toArray() + ); + + $this->data['percentage'] = 0.9; + $this->data['version'] = 2; + + $this->assertEquals( + $this->data, + $changedValueObjects->toArray() + ); + } + + /** + * @test + */ + public function it_equals_other_record_with_same_values() + { + $valueObjects = TypeHintedImmutableRecordWithValueObjects::fromArray($this->data); + $other = TypeHintedImmutableRecordWithValueObjects::fromArray($this->data); + + $this->assertTrue($valueObjects->equals($other)); + } + + /** + * @test + */ + public function it_supports_special_keys(): void + { + // emulates snake_case + $recordArray = [ + RecordWithSpecialKey::BANK_ACCOUNT => '12324434', + RecordWithSpecialKey::SUCCESS_RATE => 33.33, + RecordWithSpecialKey::ITEM_LIST => [['name' => 'Awesome tester'], ['name' => 'John Smith']], + RecordWithSpecialKey::ITEM_ARRAY => [['name' => 'Awesome tester array'], ['name' => 'John Smith array']], + + ]; + $specialKey = RecordWithSpecialKey::fromArray($recordArray); + $this->assertSame($recordArray, $specialKey->toArray()); + + $specialKey = RecordWithSpecialKey::fromRecordData([ + RecordWithSpecialKey::BANK_ACCOUNT => $recordArray[RecordWithSpecialKey::BANK_ACCOUNT], + RecordWithSpecialKey::SUCCESS_RATE => Percentage::fromFloat($recordArray[RecordWithSpecialKey::SUCCESS_RATE]), + RecordWithSpecialKey::ITEM_LIST => ItemList::fromArray($recordArray[RecordWithSpecialKey::ITEM_LIST]), + RecordWithSpecialKey::ITEM_ARRAY => array_map( + static function (array $item) { + return ImmutableItem::fromArray($item); + }, + $recordArray[RecordWithSpecialKey::ITEM_ARRAY] + ), + ]); + $this->assertSame($recordArray, $specialKey->toArray()); + } + + /** + * @test + */ + public function it_throws_exception_if_unkown_property_provided() + { + $this->data['unknown'] = 'value'; + + $this->expectExceptionMessage('Invalid property passed to Record ' . TypeHintedImmutableRecordWithValueObjects::class . '. Got property with key unknown'); + + TypeHintedImmutableRecordWithValueObjects::fromArray($this->data); + } + + /** + * @test + */ + public function it_throws_exception_if_non_nullable_prop_is_missing() + { + unset($this->data['version']); + + $this->expectExceptionMessage('Missing record data for key version of record ' . TypeHintedImmutableRecord::class); + + TypeHintedImmutableRecord::fromArray($this->data); + } + + /** + * @test + */ + public function it_throws_exception_if_non_nullable_prop_should_be_set_to_null() + { + $this->data['version'] = null; + + $this->expectExceptionMessage('Got null for non nullable property version of Record ' . TypeHintedImmutableRecord::class); + + TypeHintedImmutableRecord::fromArray($this->data); + } + + /** + * @test + */ + public function it_throws_exception_if_property_value_has_wrong_type() + { + $this->data['version'] = 'v1'; + + $this->expectExceptionMessage(\sprintf( + 'Record %s data contains invalid value for property version. Expected type is int. Got type string.', + TypeHintedImmutableRecord::class + )); + + TypeHintedImmutableRecord::fromArray($this->data); + } + + /** + * @test + */ + public function it_throws_exception_if_array_property_contains_invalid_value() + { + $stringList = ['abc', 123, 'def']; + + $this->expectExceptionMessage(\sprintf( + 'Record %s data contains invalid value for property stringList. Value should be an array of string, but at least one item of the array has the wrong type.', + RecordWithStringList::class + )); + + RecordWithStringList::fromArray(['stringList' => $stringList]); + } + /** * @test */ diff --git a/tests/Stub/ImmutableItem.php b/tests/Stub/ImmutableItem.php new file mode 100644 index 0000000..92df681 --- /dev/null +++ b/tests/Stub/ImmutableItem.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub; + +use EventEngine\Data\ImmutableRecord; +use EventEngine\Data\ImmutableRecordLogic; + +final class ImmutableItem implements ImmutableRecord +{ + use ImmutableRecordLogic; + + private $name; + + public function name(): string + { + return $this->name; + } +} diff --git a/tests/Stub/ImmutableRecordWithNoTypes.php b/tests/Stub/ImmutableRecordWithNoTypes.php index 202355c..8ac85ef 100644 --- a/tests/Stub/ImmutableRecordWithNoTypes.php +++ b/tests/Stub/ImmutableRecordWithNoTypes.php @@ -1,5 +1,11 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ declare(strict_types=1); namespace EventEngineTest\Data\Stub; @@ -14,6 +20,9 @@ final class ImmutableRecordWithNoTypes implements ImmutableRecord private $name; private $type; private $version; + private $itemList; + private $access; + private $percentage; public function name() { @@ -29,4 +38,19 @@ public function version() { return $this->version; } + + public function itemList() + { + return $this->itemList; + } + + public function access() + { + return $this->access; + } + + public function percentage() + { + return $this->percentage; + } } diff --git a/tests/Stub/ImmutableRecordWithTypedGetters.php b/tests/Stub/ImmutableRecordWithTypedGetters.php index 20553bc..85ea424 100644 --- a/tests/Stub/ImmutableRecordWithTypedGetters.php +++ b/tests/Stub/ImmutableRecordWithTypedGetters.php @@ -1,5 +1,11 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ declare(strict_types=1); namespace EventEngineTest\Data\Stub; @@ -14,6 +20,21 @@ final class ImmutableRecordWithTypedGetters implements ImmutableRecord private $name; private $type; private $version; + private $itemList; + private $access; + private $percentage; + + private static function arrayPropItemTypeMap(): array + { + return ['itemList' => ImmutableItem::class]; + } + + private function init(): void + { + if(null === $this->percentage) { + $this->percentage = 0.5; + } + } public function name() : string { @@ -29,4 +50,19 @@ public function version() : int { return $this->version; } + + public function itemList(): array + { + return $this->itemList; + } + + public function access(): bool + { + return $this->access; + } + + public function percentage(): float + { + return $this->percentage; + } } diff --git a/tests/Stub/RecordWithSpecialKey.php b/tests/Stub/RecordWithSpecialKey.php new file mode 100644 index 0000000..a7b7249 --- /dev/null +++ b/tests/Stub/RecordWithSpecialKey.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub; + +use EventEngine\Data\ImmutableRecord; +use EventEngine\Data\ImmutableRecordLogic; +use EventEngine\Data\SpecialKeySupport; +use EventEngineTest\Data\Stub\ValueObject\ItemList; +use EventEngineTest\Data\Stub\ValueObject\Percentage; + +final class RecordWithSpecialKey implements ImmutableRecord, SpecialKeySupport +{ + use ImmutableRecordLogic; + + public const BANK_ACCOUNT = 'bank_account'; + public const SUCCESS_RATE = 'success_rate'; + public const ITEM_LIST = 'item_list'; + public const ITEM_ARRAY = 'item_array'; + + /** + * @var string + */ + private $bankAccount; + + /** + * @var Percentage + */ + private $successRate; + + /** + * @var ItemList + */ + private $itemList; + + /** + * @var array + */ + private $itemArray; + + /** + * @return mixed + */ + public function bankAccount(): string + { + return $this->bankAccount; + } + + /** + * @return Percentage + */ + public function successRate(): Percentage + { + return $this->successRate; + } + + /** + * @return ItemList + */ + public function itemList(): ItemList + { + return $this->itemList; + } + + /** + * @return array + */ + public function itemArray(): array + { + return $this->itemArray; + } + + public function convertKeyForRecord(string $key): string + { + switch ($key) { + case self::SUCCESS_RATE: + return 'successRate'; + case self::ITEM_LIST: + return 'itemList'; + case self::ITEM_ARRAY: + return 'itemArray'; + default: + return 'bankAccount'; + } + } + + public function convertKeyForArray(string $key): string + { + switch ($key) { + case 'successRate': + return self::SUCCESS_RATE; + case 'itemList': + return self::ITEM_LIST; + case 'itemArray': + return self::ITEM_ARRAY; + default: + return self::BANK_ACCOUNT; + } + } + + private static function arrayPropItemTypeMap(): array + { + return [ + 'itemArray' => ImmutableItem::class, + ]; + } +} diff --git a/tests/Stub/RecordWithStringList.php b/tests/Stub/RecordWithStringList.php new file mode 100644 index 0000000..059d359 --- /dev/null +++ b/tests/Stub/RecordWithStringList.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub; + +use EventEngine\Data\ImmutableRecord; +use EventEngine\Data\ImmutableRecordLogic; + +final class RecordWithStringList implements ImmutableRecord +{ + use ImmutableRecordLogic; + + private array $stringList; + + private static function arrayPropItemTypeMap(): array + { + return ['stringList' => ImmutableRecord::PHP_TYPE_STRING]; + } + + /** + * @return array + */ + public function stringList(): array + { + return $this->stringList; + } +} diff --git a/tests/Stub/TypeHintedImmutableRecord.php b/tests/Stub/TypeHintedImmutableRecord.php index df58fef..5ae44d5 100644 --- a/tests/Stub/TypeHintedImmutableRecord.php +++ b/tests/Stub/TypeHintedImmutableRecord.php @@ -1,5 +1,11 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ declare(strict_types=1); namespace EventEngineTest\Data\Stub; @@ -14,19 +20,67 @@ final class TypeHintedImmutableRecord implements ImmutableRecord private string $name; private ?string $type = null; private int $version; + private array $itemList; + private bool $access; + private float $percentage; + + private static function arrayPropItemTypeMap(): array + { + return ['itemList' => ImmutableItem::class]; + } - public function name() + private function init(): void + { + if(!isset($this->percentage)) { + $this->percentage = 0.5; + } + } + + /** + * @return string + */ + public function name(): string { return $this->name; } - public function type() + /** + * @return string|null + */ + public function type(): ?string { return $this->type; } - public function version() + /** + * @return int + */ + public function version(): int { return $this->version; } + + /** + * @return array + */ + public function itemList(): array + { + return $this->itemList; + } + + /** + * @return bool + */ + public function access(): bool + { + return $this->access; + } + + /** + * @return float + */ + public function percentage(): float + { + return $this->percentage; + } } diff --git a/tests/Stub/TypeHintedImmutableRecordWithValueObjects.php b/tests/Stub/TypeHintedImmutableRecordWithValueObjects.php new file mode 100644 index 0000000..07909ce --- /dev/null +++ b/tests/Stub/TypeHintedImmutableRecordWithValueObjects.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub; + +use EventEngine\Data\ImmutableRecord; +use EventEngine\Data\ImmutableRecordLogic; +use EventEngineTest\Data\Stub\ValueObject\Access; +use EventEngineTest\Data\Stub\ValueObject\ItemList; +use EventEngineTest\Data\Stub\ValueObject\Name; +use EventEngineTest\Data\Stub\ValueObject\Percentage; +use EventEngineTest\Data\Stub\ValueObject\Type; +use EventEngineTest\Data\Stub\ValueObject\Version; + +final class TypeHintedImmutableRecordWithValueObjects implements ImmutableRecord +{ + use ImmutableRecordLogic; + + private Name $name; + + private ?Type $type; + + private Version $version; + + private ItemList $itemList; + + private Access $access; + + private Percentage $percentage; + + private function init(): void + { + if(!isset($this->percentage)) { + $this->percentage = Percentage::fromFloat(0.5); + } + } + + /** + * @return Name + */ + public function name(): Name + { + return $this->name; + } + + /** + * @return Type|null + */ + public function type(): ?Type + { + return $this->type; + } + + /** + * @return Version + */ + public function version(): Version + { + return $this->version; + } + + /** + * @return ItemList + */ + public function itemList(): ItemList + { + return $this->itemList; + } + + /** + * @return Access + */ + public function access(): Access + { + return $this->access; + } + + /** + * @return Percentage + */ + public function percentage(): Percentage + { + return $this->percentage; + } +} diff --git a/tests/Stub/ValueObject/Access.php b/tests/Stub/ValueObject/Access.php new file mode 100644 index 0000000..cf7382b --- /dev/null +++ b/tests/Stub/ValueObject/Access.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub\ValueObject; + +final class Access +{ + private $access; + + public static function fromBool(bool $access): self + { + return new self($access); + } + + private function __construct(bool $access) + { + $this->access = $access; + } + + public function toBool(): bool + { + return $this->access; + } + + /** + * @param mixed $other + */ + public function equals($other): bool + { + if(!$other instanceof self) { + return false; + } + + return $this->access === $other->access; + } + + public function __toString(): string + { + return $this->access ? 'TRUE' : 'FALSE'; + } +} diff --git a/tests/Stub/ValueObject/ItemList.php b/tests/Stub/ValueObject/ItemList.php new file mode 100644 index 0000000..0bfc0a1 --- /dev/null +++ b/tests/Stub/ValueObject/ItemList.php @@ -0,0 +1,129 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub\ValueObject; + +use EventEngineTest\Data\Stub\ImmutableItem; + +final class ItemList +{ + /** + * @var ImmutableItem[] + */ + private $items; + + public static function fromArray(array $items): self + { + return new self(...array_map(function (array $item) { + return ImmutableItem::fromArray($item); + }, $items)); + } + + public static function fromItems(ImmutableItem ...$items): self + { + return new self(...$items); + } + + public static function emptyList(): self + { + return new self(); + } + + private function __construct(ImmutableItem ...$items) + { + $this->items = $items; + } + + public function push(ImmutableItem $item): self + { + $copy = clone $this; + $copy->items[] = $item; + return $copy; + } + + public function pop(): self + { + $copy = clone $this; + \array_pop($copy->items); + return $copy; + } + + public function first(): ?ImmutableItem + { + return $this->items[0] ?? null; + } + + public function last(): ?ImmutableItem + { + if (count($this->items) === 0) { + return null; + } + + return $this->items[count($this->items) - 1]; + } + + public function contains(ImmutableItem $item): bool + { + foreach ($this->items as $existingItem) { + if ($existingItem->equals($item)) { + return true; + } + } + + return false; + } + + public function filter(callable $filter): self + { + $filteredItems = []; + + foreach ($this->items as $item) { + if ($filter($item)) { + $filteredItems[] = $item; + } + } + + $copy = clone $this; + $copy->items = $filteredItems; + return $copy; + } + + /** + * @return ImmutableItem[] + */ + public function items(): array + { + return $this->items; + } + + public function toArray(): array + { + return \array_map(function (ImmutableItem $item) { + return $item->toArray(); + }, $this->items); + } + + /** + * @param mixed $other + */ + public function equals($other): bool + { + if (!$other instanceof self) { + return false; + } + + return $this->toArray() === $other->toArray(); + } + + public function __toString(): string + { + return \json_encode($this->toArray()); + } +} diff --git a/tests/Stub/ValueObject/Name.php b/tests/Stub/ValueObject/Name.php new file mode 100644 index 0000000..342b3c0 --- /dev/null +++ b/tests/Stub/ValueObject/Name.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub\ValueObject; + +final class Name +{ + private $name; + + public static function fromString(string $name): self + { + return new self($name); + } + + private function __construct(string $name) + { + $this->name = $name; + } + + public function toString(): string + { + return $this->name; + } + + /** + * @param mixed $other + */ + public function equals($other): bool + { + if(!$other instanceof self) { + return false; + } + + return $this->name === $other->name; + } + + public function __toString(): string + { + return $this->name; + } +} diff --git a/tests/Stub/ValueObject/Percentage.php b/tests/Stub/ValueObject/Percentage.php new file mode 100644 index 0000000..145b3ac --- /dev/null +++ b/tests/Stub/ValueObject/Percentage.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub\ValueObject; + +final class Percentage +{ + private $percentage; + + public static function fromFloat(float $percentage): self + { + return new self($percentage); + } + + private function __construct(float $percentage) + { + $this->percentage = $percentage; + } + + public function toFloat(): float + { + return $this->percentage; + } + + /** + * @param mixed $other + */ + public function equals($other): bool + { + if(!$other instanceof self) { + return false; + } + + return $this->percentage === $other->percentage; + } + + public function __toString(): string + { + return (string)$this->percentage; + } +} diff --git a/tests/Stub/ValueObject/Type.php b/tests/Stub/ValueObject/Type.php new file mode 100644 index 0000000..ff8c2ae --- /dev/null +++ b/tests/Stub/ValueObject/Type.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub\ValueObject; + +final class Type +{ + private $type; + + public static function fromString(string $type): self + { + return new self($type); + } + + private function __construct(string $type) + { + $this->type = $type; + } + + public function toString(): string + { + return $this->type; + } + + /** + * @param mixed $other + */ + public function equals($other): bool + { + if(!$other instanceof self) { + return false; + } + + return $this->type === $other->type; + } + + public function __toString(): string + { + return $this->type; + } +} diff --git a/tests/Stub/ValueObject/Version.php b/tests/Stub/ValueObject/Version.php new file mode 100644 index 0000000..971f970 --- /dev/null +++ b/tests/Stub/ValueObject/Version.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ +declare(strict_types=1); + +namespace EventEngineTest\Data\Stub\ValueObject; + +final class Version +{ + private $version; + + public static function fromInt(int $version): self + { + return new self($version); + } + + private function __construct(int $version) + { + $this->version = $version; + } + + public function toInt(): int + { + return $this->version; + } + + /** + * @param mixed $other + */ + public function equals($other): bool + { + if(!$other instanceof self) { + return false; + } + + return $this->version === $other->version; + } + + public function __toString(): string + { + return (string)$this->version; + } +}