From 2e9c072be861f6e02c7b43da42574c815e6ed884 Mon Sep 17 00:00:00 2001 From: Taras Chornyi Date: Sun, 17 Sep 2023 15:23:23 +0300 Subject: [PATCH 1/6] - Fix nullable fields serializing - Increase test coverage - Refactor - Add github actions workflow --- .github/workflows/php.yaml | 51 ++++++ .gitignore | 3 +- composer.json | 1 + phpstan.neon | 3 +- phpunit.xml | 25 +-- src/Serializer/ObjectSerializer.php | 15 +- src/Serializer/ObjectSerializerInterface.php | 2 + src/Serializer/Serializer/ArraySerializer.php | 11 +- src/Serializer/Serializer/JsonSerializer.php | 16 +- src/Transformer/Transformer.php | 118 ++++++++------ .../FakeObjectWithConstructorAndGetters.php | 10 ++ tests/Fake/Item/FakeSimpleObject.php | 2 + .../Item/FakeSimpleObjectWithConstructor.php | 4 +- .../FakeSimpleObjectWithForcedSerializer.php | 18 +++ tests/Fake/RandomUserApi/Coordinates.php | 12 ++ tests/Fake/RandomUserApi/Location.php | 22 +++ tests/Fake/RandomUserApi/Name.php | 9 +- tests/Fake/RandomUserApi/RandomUser.php | 13 +- tests/Fake/RandomUserApi/Street.php | 12 ++ tests/Fake/RandomUserApi/Timezone.php | 12 ++ .../RandomUserApiResponseTransformerTest.php | 26 +++- tests/RandomUserApi/_mock/mock.json | 146 +++++++++++++++++- tests/Serializer/CollectionSerializerTest.php | 48 +++++- tests/Serializer/GeneratorSerializerTest.php | 43 +++++- tests/Serializer/JsonSerializerTest.php | 22 ++- tests/Serializer/ObjectSerializerTest.php | 39 ++++- .../Transformer/GeneratorTransformerTest.php | 13 +- tests/Transformer/ObjectTransformerTest.php | 18 ++- 28 files changed, 610 insertions(+), 104 deletions(-) create mode 100644 .github/workflows/php.yaml create mode 100644 tests/Fake/Item/FakeSimpleObjectWithForcedSerializer.php create mode 100644 tests/Fake/RandomUserApi/Coordinates.php create mode 100644 tests/Fake/RandomUserApi/Location.php create mode 100644 tests/Fake/RandomUserApi/Street.php create mode 100644 tests/Fake/RandomUserApi/Timezone.php diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml new file mode 100644 index 0000000..d57ce0f --- /dev/null +++ b/.github/workflows/php.yaml @@ -0,0 +1,51 @@ +name: PHP Build + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + - name: Run test suite + run: composer test + + - name: Make code coverage badge + uses: timkrase/phpunit-coverage-badge@v1.2.1 + with: + coverage_badge_path: output/coverage.svg + push_badge: false + + - name: Git push to image-data branch + uses: peaceiris/actions-gh-pages@v3 + with: + publish_dir: ./output + publish_branch: image-data + github_token: ${{ secrets.GITHUB_TOKEN }} + user_name: 'github-actions[bot]' + user_email: 'github-actions[bot]@users.noreply.github.com' diff --git a/.gitignore b/.gitignore index 31ffbdd..2e6edc5 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /.idea -/.fleet /vendor /.phpunit.result.cache /.php-cs-fixer.cache +/.phpunit.cache +/clover.xml \ No newline at end of file diff --git a/composer.json b/composer.json index 71e091a..e8faa95 100755 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "test": [ "@putenv PHP_CS_FIXER_IGNORE_ENV=true", "php-cs-fixer fix --dry-run", + "@putenv XDEBUG_MODE=coverage", "phpunit", "phpstan --xdebug", "psalm" diff --git a/phpstan.neon b/phpstan.neon index b999b4e..ff4d5c2 100755 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,5 @@ parameters: level: 6 paths: - - src \ No newline at end of file + - src + checkGenericClassInNonGenericObjectType: false \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index dd27d00..ab15299 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,11 +1,18 @@ - - - - - tests - - + + + + + + + + + tests + + + + + src + + diff --git a/src/Serializer/ObjectSerializer.php b/src/Serializer/ObjectSerializer.php index fce0fba..86664fc 100755 --- a/src/Serializer/ObjectSerializer.php +++ b/src/Serializer/ObjectSerializer.php @@ -15,12 +15,25 @@ */ class ObjectSerializer implements ObjectSerializerInterface { + public function __construct(private bool $includePrivateProperties = false) + { + } + public function serialize(object $object, ObjectSerializerInterface|string|null $originSerializer = null): mixed { $reflection = new ReflectionClass($object); $objectItem = $this->getTransformerObject($reflection); - return $this->getObjectSerializer($objectItem, $originSerializer)->serialize($object, $this); + return $this->getObjectSerializer($objectItem, $originSerializer) + ->setIncludePrivateProperties($this->includePrivateProperties) + ->serialize($object, $this); + } + + public function setIncludePrivateProperties(bool $includePrivateProperties): self + { + $this->includePrivateProperties = $includePrivateProperties; + + return $this; } private function getObjectSerializer( diff --git a/src/Serializer/ObjectSerializerInterface.php b/src/Serializer/ObjectSerializerInterface.php index 080f462..26256a3 100755 --- a/src/Serializer/ObjectSerializerInterface.php +++ b/src/Serializer/ObjectSerializerInterface.php @@ -10,4 +10,6 @@ interface ObjectSerializerInterface * @param ObjectSerializerInterface|class-string|null $originSerializer */ public function serialize(object $object, ObjectSerializerInterface|string|null $originSerializer = null): mixed; + + public function setIncludePrivateProperties(bool $includePrivateProperties): self; } diff --git a/src/Serializer/Serializer/ArraySerializer.php b/src/Serializer/Serializer/ArraySerializer.php index 059b180..2078bbf 100755 --- a/src/Serializer/Serializer/ArraySerializer.php +++ b/src/Serializer/Serializer/ArraySerializer.php @@ -17,6 +17,8 @@ class ArraySerializer implements ObjectSerializerInterface use Fieldable; use Valuable; + private bool $includePrivateProperties = false; + /** * @param ObjectSerializerInterface|class-string|null $originSerializer * @return array @@ -30,7 +32,7 @@ public function serialize(object $object, ObjectSerializerInterface|string|null foreach ($properties as $property) { $field = $this->getPropertyAttribute($property); - if ($field->ignoreSerialize) { + if (!$this->includePrivateProperties && $field->ignoreSerialize) { continue; } @@ -41,6 +43,13 @@ public function serialize(object $object, ObjectSerializerInterface|string|null return $serialized; } + public function setIncludePrivateProperties(bool $includePrivateProperties): self + { + $this->includePrivateProperties = $includePrivateProperties; + + return $this; + } + /** * @param ObjectSerializerInterface|class-string|null $serializer */ diff --git a/src/Serializer/Serializer/JsonSerializer.php b/src/Serializer/Serializer/JsonSerializer.php index 2713f0a..5f12304 100644 --- a/src/Serializer/Serializer/JsonSerializer.php +++ b/src/Serializer/Serializer/JsonSerializer.php @@ -14,6 +14,8 @@ class JsonSerializer implements ObjectSerializerInterface use Fieldable; use Valuable; + private bool $includePrivateProperties = false; + /** * @param ObjectSerializerInterface|class-string|null $originSerializer */ @@ -21,6 +23,18 @@ public function serialize(object $object, string|ObjectSerializerInterface|null { $arraySerializer = new ArraySerializer(); - return json_encode($arraySerializer->serialize($object, $originSerializer)); + return json_encode( + $arraySerializer + ->setIncludePrivateProperties($this->includePrivateProperties) + ->serialize($object, $originSerializer), + JSON_THROW_ON_ERROR + ); + } + + public function setIncludePrivateProperties(bool $includePrivateProperties): self + { + $this->includePrivateProperties = $includePrivateProperties; + + return $this; } } diff --git a/src/Transformer/Transformer.php b/src/Transformer/Transformer.php index 238f1d0..2e00db0 100755 --- a/src/Transformer/Transformer.php +++ b/src/Transformer/Transformer.php @@ -17,6 +17,10 @@ use ReflectionParameter; +use ReflectionException; + +use ReflectionType; + use function array_key_exists; use function class_exists; use function is_string; @@ -58,23 +62,10 @@ public function transform(string|object $object, iterable $data): object return $object; } - $args = []; - foreach ($parameters as $parameter) { - $field = $this->getParameterAttribute($parameter); - $name = $field->nameIn ?? $parameter->getName(); - - if (array_key_exists($name, $this->data)) { - $args[$name] = $this->getValue($field, $parameter, $name); - } - } - - $object = $reflection->newInstanceWithoutConstructor(); - $reflection->getConstructor()?->invokeArgs($object, $args); - - return $object; + return $this->handleConstructorParameters($parameters, $reflection); } - public function setIncludePrivateProperties(bool $includePrivateProperties): Transformer + public function setIncludePrivateProperties(bool $includePrivateProperties): self { $this->includePrivateProperties = $includePrivateProperties; @@ -126,37 +117,7 @@ private function getValue(DataField $field, ReflectionProperty|ReflectionParamet return $this->getValueFromObjectResolver($field, $reflectionType, $name); } - $value = $this->data[$name]; - - if ($property->hasType() && $reflectionType instanceof ReflectionNamedType) { - if ($value === null && $reflectionType->allowsNull()) { - return null; - } - - if ($reflectionType->isBuiltin()) { - settype($value, $reflectionType->getName()); - - return $value; - } - - if (enum_exists($reflectionType->getName())) { - /** @var class-string|class-string $enum */ - $enum = $reflectionType->getName(); - if (is_subclass_of($enum, BackedEnum::class)) { - return $enum::tryFrom($value); - } - - $cases = array_values(array_filter($enum::cases(), fn (UnitEnum $unitEnum) => $unitEnum->name === $value)); - - return $cases[0] ?? throw new TransformerException(sprintf('No enum case found for %s in %s', $value, $enum)); - } - - if (class_exists($reflectionType->getName())) { - return (new self())->transform($reflectionType->getName(), $value); - } - } - - return $value; + return $this->getTypedValue($property, $reflectionType, $this->data[$name]); } /** @@ -202,10 +163,73 @@ private function getValueFromObjectResolver( ): mixed { /** @var TransformerInterface $objectTransformer */ $objectTransformer = is_string($field->objectTransformer) ? new $field->objectTransformer() : $field->objectTransformer; - /** @var class-string $objectClass */ $objectClass = $this->getType($reflectionType); return $objectTransformer->transform($objectClass, $this->data[$name]); } + + /** + * @param array $parameters + * @param ReflectionClass $reflection + * @throws ReflectionException + */ + private function handleConstructorParameters(array $parameters, ReflectionClass $reflection): object + { + $args = []; + foreach ($parameters as $parameter) { + $field = $this->getParameterAttribute($parameter); + $name = $field->nameIn ?? $parameter->getName(); + + if (array_key_exists($name, $this->data)) { + $args[$name] = $this->getValue($field, $parameter, $name); + } + } + + $object = $reflection->newInstanceWithoutConstructor(); + $reflection->getConstructor()?->invokeArgs($object, $args); + + return $object; + } + + private function getTypedValue( + ReflectionParameter|ReflectionProperty $property, + ?ReflectionType $reflectionType, + mixed $value + ): mixed { + if ($property->hasType() && $reflectionType instanceof ReflectionNamedType) { + if ($value === null && $reflectionType->allowsNull()) { + return null; + } + + if ($reflectionType->isBuiltin()) { + settype($value, $reflectionType->getName()); + + return $value; + } + + if (enum_exists($reflectionType->getName())) { + return $this->getEnumValue($reflectionType, $value); + } + + if (class_exists($reflectionType->getName())) { + return (new self())->transform($reflectionType->getName(), $value); + } + } + + return $value; + } + + private function getEnumValue(ReflectionNamedType $reflectionType, mixed $value): mixed + { + /** @var class-string|class-string $enum */ + $enum = $reflectionType->getName(); + if (is_subclass_of($enum, BackedEnum::class)) { + return $enum::tryFrom($value); + } + + $cases = array_values(array_filter($enum::cases(), fn (UnitEnum $unitEnum) => $unitEnum->name === $value)); + + return $cases[0] ?? throw new TransformerException(sprintf('No enum case found for %s in %s', $value, $enum)); + } } diff --git a/tests/Fake/Item/FakeObjectWithConstructorAndGetters.php b/tests/Fake/Item/FakeObjectWithConstructorAndGetters.php index 148f1a3..299a715 100644 --- a/tests/Fake/Item/FakeObjectWithConstructorAndGetters.php +++ b/tests/Fake/Item/FakeObjectWithConstructorAndGetters.php @@ -35,4 +35,14 @@ public function getPostcode(): int { return $this->postcode; } + + public function setPostcode(int $postcode): void + { + $this->postcode = $postcode; + } + + public function setCity(string $city): void + { + $this->city = $city; + } } diff --git a/tests/Fake/Item/FakeSimpleObject.php b/tests/Fake/Item/FakeSimpleObject.php index d3dbb82..5947299 100755 --- a/tests/Fake/Item/FakeSimpleObject.php +++ b/tests/Fake/Item/FakeSimpleObject.php @@ -17,6 +17,8 @@ class FakeSimpleObject #[DataField(ignoreTransform: true)] public string $description; + public $unknownType = null; + public function __construct() { $this->description = 'My fake description'; diff --git a/tests/Fake/Item/FakeSimpleObjectWithConstructor.php b/tests/Fake/Item/FakeSimpleObjectWithConstructor.php index 198c260..d81a81f 100755 --- a/tests/Fake/Item/FakeSimpleObjectWithConstructor.php +++ b/tests/Fake/Item/FakeSimpleObjectWithConstructor.php @@ -9,10 +9,10 @@ class FakeSimpleObjectWithConstructor { public function __construct( - #[DataField] public readonly int $id, - #[DataField] public readonly string $label, + #[DataField(ignoreSerialize: true)] + public readonly bool $hidden = true ) { } } diff --git a/tests/Fake/Item/FakeSimpleObjectWithForcedSerializer.php b/tests/Fake/Item/FakeSimpleObjectWithForcedSerializer.php new file mode 100644 index 0000000..fef296b --- /dev/null +++ b/tests/Fake/Item/FakeSimpleObjectWithForcedSerializer.php @@ -0,0 +1,18 @@ + $collection */ $collection = $transformer->transform(RandomUser::class, $users); self::assertCount(count($users), $collection); self::assertContainsOnlyInstancesOf(RandomUser::class, $collection); - self::assertSame($users[0]['name']['first'], $collection[0]->name->firstName); - self::assertSame($users[0]['name']['last'], $collection[0]->name->lastName); - self::assertSame($users[0]['name']['title'], $collection[0]->name->title); + + foreach ($users as $i => $user) { + $randomUser = $collection[$i]; + self::assertSame($user['name']['first'], $randomUser->name->firstName); + self::assertSame($user['name']['last'], $randomUser->name->lastName); + self::assertSame($user['name']['title'], $randomUser->name->title); + self::assertSame($user['phone'], $randomUser->phone); + self::assertSame($user['email'], $randomUser->email); + self::assertSame($user['location']['city'], $randomUser->location->city); + self::assertSame($user['location']['state'], $randomUser->location->state); + self::assertSame($user['location']['country'], $randomUser->location->country); + self::assertSame($user['location']['postcode'], $randomUser->location->postcode); + self::assertSame($user['location']['street']['name'], $randomUser->location->street->name); + self::assertSame($user['location']['street']['number'], $randomUser->location->street->number); + self::assertSame($user['location']['coordinates']['latitude'], $randomUser->location->coordinates->latitude); + self::assertSame($user['location']['coordinates']['longitude'], $randomUser->location->coordinates->longitude); + self::assertSame($user['location']['timezone']['offset'], $randomUser->location->timezone->offset); + self::assertSame($user['location']['timezone']['description'], $randomUser->location->timezone->description); + } + } } diff --git a/tests/RandomUserApi/_mock/mock.json b/tests/RandomUserApi/_mock/mock.json index 0cebf31..c9d1046 100755 --- a/tests/RandomUserApi/_mock/mock.json +++ b/tests/RandomUserApi/_mock/mock.json @@ -1 +1,145 @@ -{"results":[{"name":{"title":"Miss","first":"Kaitlin","last":"Sullivan"},"email":"kaitlin.sullivan@example.com"},{"name":{"title":"Mr","first":"Levi","last":"Wagner"},"email":"levi.wagner@example.com"},{"name":{"title":"Mr","first":"Morris","last":"Lewis"},"email":"morris.lewis@example.com"},{"name":{"title":"Mr","first":"Raul","last":"Long"},"email":"raul.long@example.com"},{"name":{"title":"Mr","first":"Louis","last":"Adams"},"email":"louis.adams@example.com"}],"info":{"seed":"16f7b959f957a523","results":5,"page":1,"version":"1.4"}} \ No newline at end of file +{ + "results": [ + { + "name": { + "title": "Ms", + "first": "Caroline", + "last": "Holland" + }, + "location": { + "street": { + "number": 3869, + "name": "Pockrus Page Rd" + }, + "city": "Fort Lauderdale", + "state": "Nevada", + "country": "United States", + "postcode": 79273, + "coordinates": { + "latitude": "-86.8445", + "longitude": "136.7738" + }, + "timezone": { + "offset": "-10:00", + "description": "Hawaii" + } + }, + "email": "caroline.holland@example.com", + "phone": "(603) 394-1423" + }, + { + "name": { + "title": "Mr", + "first": "Julio", + "last": "Olson" + }, + "location": { + "street": { + "number": 6145, + "name": "College St" + }, + "city": "Henderson", + "state": "Rhode Island", + "country": "United States", + "postcode": 26699, + "coordinates": { + "latitude": "-12.4752", + "longitude": "-80.3245" + }, + "timezone": { + "offset": "-9:00", + "description": "Alaska" + } + }, + "email": "julio.olson@example.com", + "phone": "(863) 254-7163" + }, + { + "name": { + "title": "Ms", + "first": "Suzanna", + "last": "Alexander" + }, + "location": { + "street": { + "number": 5451, + "name": "Oaks Cross" + }, + "city": "Newcastle upon Tyne", + "state": "Wiltshire", + "country": "United Kingdom", + "postcode": "FR01 0WG", + "coordinates": { + "latitude": "-38.5066", + "longitude": "21.5725" + }, + "timezone": { + "offset": "0:00", + "description": "Western Europe Time, London, Lisbon, Casablanca" + } + }, + "email": "suzanna.alexander@example.com", + "phone": "021 3142 5913" + }, + { + "name": { + "title": "Mr", + "first": "Jesse", + "last": "Patterson" + }, + "location": { + "street": { + "number": 472, + "name": "Queen Street" + }, + "city": "Belfast", + "state": "Norfolk", + "country": "United Kingdom", + "postcode": "DF8D 6YR", + "coordinates": { + "latitude": "10.6227", + "longitude": "171.0579" + }, + "timezone": { + "offset": "-4:00", + "description": "Atlantic Time (Canada), Caracas, La Paz" + } + }, + "email": "jesse.patterson@example.com", + "phone": "016977 1491" + }, + { + "name": { + "title": "Mr", + "first": "Rodney", + "last": "Ortiz" + }, + "location": { + "street": { + "number": 2989, + "name": "Locust Rd" + }, + "city": "Vernon", + "state": "Colorado", + "country": "United States", + "postcode": 18296, + "coordinates": { + "latitude": "16.0667", + "longitude": "123.5898" + }, + "timezone": { + "offset": "-11:00", + "description": "Midway Island, Samoa" + } + }, + "email": "rodney.ortiz@example.com", + "phone": "(876) 913-3377" + } + ], + "info": { + "seed": "f34a64395df5a22c", + "results": 5, + "page": 1, + "version": "1.4" + } +} \ No newline at end of file diff --git a/tests/Serializer/CollectionSerializerTest.php b/tests/Serializer/CollectionSerializerTest.php index cf7485e..0cd25e5 100755 --- a/tests/Serializer/CollectionSerializerTest.php +++ b/tests/Serializer/CollectionSerializerTest.php @@ -5,25 +5,57 @@ namespace Blacktrs\DataTransformer\Tests\Serializer; use Blacktrs\DataTransformer\Serializer\CollectionSerializer; +use Blacktrs\DataTransformer\Serializer\Serializer\{ArraySerializer, JsonSerializer}; use Blacktrs\DataTransformer\Tests\Fake\Item\FakeSimpleObjectWithConstructor; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; +use Generator; + use function count; class CollectionSerializerTest extends TestCase { - public function testArrayCollectionSerialize(): void + private CollectionSerializer $serializer; + + protected function setUp(): void { - $serializer = new CollectionSerializer(); - $data = [ - new FakeSimpleObjectWithConstructor(123, 'First label'), - new FakeSimpleObjectWithConstructor(1000, 'Second label') - ]; + $this->serializer = new CollectionSerializer(); + } - $result = $serializer->serialize($data); + #[DataProvider('collectionDataProvider')] + public function testArrayCollectionSerialize(array $collection): void + { + $result = $this->serializer->serialize($collection); - self::assertCount(count($data), $result); + self::assertCount(count($collection), $result); self::assertIsArray($result[0]); self::assertIsArray($result[1]); } + + public function testArrayEmptyCollection(): void + { + $result = $this->serializer->serialize([]); + + static::assertIsArray($result); + static::assertEmpty($result); + } + + #[DataProvider('collectionDataProvider')] + public function testArrayCollectionWithSerializerClassName(array $collection): void + { + $result = $this->serializer->serialize($collection, serializer: ArraySerializer::class); + + static::assertIsArray($result); + } + + public static function collectionDataProvider(): Generator + { + yield [ + [ + new FakeSimpleObjectWithConstructor(123, 'First label'), + new FakeSimpleObjectWithConstructor(1000, 'Second label') + ] + ]; + } } diff --git a/tests/Serializer/GeneratorSerializerTest.php b/tests/Serializer/GeneratorSerializerTest.php index 0a63c88..296674c 100644 --- a/tests/Serializer/GeneratorSerializerTest.php +++ b/tests/Serializer/GeneratorSerializerTest.php @@ -5,21 +5,40 @@ namespace Blacktrs\DataTransformer\Tests\Serializer; use Blacktrs\DataTransformer\Serializer\GeneratorSerializer; +use Blacktrs\DataTransformer\Serializer\Serializer\ArraySerializer; use Blacktrs\DataTransformer\Tests\Fake\Item\FakeSimpleObjectWithConstructor; use Generator; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class GeneratorSerializerTest extends TestCase { - public function testGeneratorSerialize(): void + private GeneratorSerializer $generatorSerializer; + + protected function setUp(): void + { + $this->generatorSerializer = new GeneratorSerializer(); + } + + #[DataProvider('collectionDataProvider')] + public function testGeneratorSerialize(array $collection): void + { + + $result = $this->generatorSerializer->serialize($collection); + + self::assertInstanceOf(Generator::class, $result); + + foreach ($result as $item) { + self::assertArrayHasKey('id', $item); + self::assertArrayHasKey('label', $item); + } + } + + #[DataProvider('collectionDataProvider')] + public function testGeneratorSerializeWithSerializerClassName(array $collection): void { - $generatorSerializer = new GeneratorSerializer(); - $data = [ - new FakeSimpleObjectWithConstructor(123, 'First label'), - new FakeSimpleObjectWithConstructor(1000, 'Second label') - ]; - $result = $generatorSerializer->serialize($data); + $result = $this->generatorSerializer->serialize($collection, serializer: ArraySerializer::class); self::assertInstanceOf(Generator::class, $result); @@ -28,4 +47,14 @@ public function testGeneratorSerialize(): void self::assertArrayHasKey('label', $item); } } + + public static function collectionDataProvider(): Generator + { + yield [ + [ + new FakeSimpleObjectWithConstructor(123, 'First label'), + new FakeSimpleObjectWithConstructor(1000, 'Second label') + ] + ]; + } } diff --git a/tests/Serializer/JsonSerializerTest.php b/tests/Serializer/JsonSerializerTest.php index 3cf5956..4053190 100644 --- a/tests/Serializer/JsonSerializerTest.php +++ b/tests/Serializer/JsonSerializerTest.php @@ -5,17 +5,33 @@ namespace Blacktrs\DataTransformer\Tests\Serializer; use Blacktrs\DataTransformer\Serializer\Serializer\JsonSerializer; -use Blacktrs\DataTransformer\Tests\Fake\Item\FakeSimpleObjectWithConstructor; +use Blacktrs\DataTransformer\Tests\Fake\Item\{FakeObjectWithConstructorAndGetters, FakeSimpleObjectWithConstructor}; use PHPUnit\Framework\TestCase; class JsonSerializerTest extends TestCase { + private JsonSerializer $serializer; + + protected function setUp(): void + { + $this->serializer = new JsonSerializer(); + } + public function testJsonSerializer(): void { $fakeObject = new FakeSimpleObjectWithConstructor(100, 'Some label'); - $serializer = new JsonSerializer(); - $result = $serializer->serialize($fakeObject); + $result = $this->serializer->serialize($fakeObject); self::assertJson($result); } + + public function testForcePrivatePropertyJsonSerialize(): void + { + $fakeObject = new FakeObjectWithConstructorAndGetters('John Doe', 42); + $fakeObject->setCity('Lviv'); + $fakeObject->setPostcode(79000); + + $result = $this->serializer->setIncludePrivateProperties(true)->serialize($fakeObject); + static::assertJson($result); + } } diff --git a/tests/Serializer/ObjectSerializerTest.php b/tests/Serializer/ObjectSerializerTest.php index ab7064b..d0de285 100755 --- a/tests/Serializer/ObjectSerializerTest.php +++ b/tests/Serializer/ObjectSerializerTest.php @@ -6,14 +6,17 @@ use Blacktrs\DataTransformer\Serializer\ObjectSerializer; use Blacktrs\DataTransformer\Tests\Fake\Enum\{FakeColorEnum, FakeSizeEnum}; -use Blacktrs\DataTransformer\Tests\Fake\Item\{FakeObjectWithEnumProperty, +use Blacktrs\DataTransformer\Tests\Fake\Item\{FakeObjectWithConstructorAndGetters, + FakeObjectWithEnumProperty, FakeObjectWithFieldResolver, FakeObjectWithFieldResolverAndArguments, FakeObjectWithGetters, FakeObjectWithOtherItem, + FakeObjectWithPrivateProperties, FakeObjectWithStringableProperty, FakeSimpleObject, FakeSimpleObjectWithConstructor, + FakeSimpleObjectWithForcedSerializer, FakeStringableObject}; use DateTime; use PHPUnit\Framework\TestCase; @@ -38,6 +41,7 @@ public function testDefaultObjectSerialize(): void self::assertIsInt($result['id']); self::assertArrayHasKey('label', $result); self::assertIsString($result['label']); + self::assertArrayNotHasKey('hidden', $result); } public function testNestedObjectSerialize(): void @@ -114,4 +118,37 @@ public function testStringableSerialize(): void self::assertArrayHasKey('data', $result); self::assertSame('String value: 12345678', $result['data']); } + + public function testForcePrivatePropertySerialize(): void + { + $fakeObject = new FakeObjectWithConstructorAndGetters('John Doe', 42); + $fakeObject->setCity('Lviv'); + $fakeObject->setPostcode(79000); + + $result = $this->serializer->setIncludePrivateProperties(true)->serialize($fakeObject); + + static::assertArrayHasKey('name', $result); + static::assertSame($fakeObject->getName(), $result['name']); + + static::assertArrayHasKey('age', $result); + static::assertSame($fakeObject->getAge(), $result['age']); + + static::assertArrayHasKey('city', $result); + static::assertSame($fakeObject->getCity(), $result['city']); + + static::assertArrayHasKey('postcode', $result); + static::assertSame($fakeObject->getPostcode(), $result['postcode']); + } + + public function testForcedJsonSerializer(): void + { + $fakeObject = new FakeSimpleObjectWithForcedSerializer(); + $fakeObject->id = 10; + $fakeObject->label = 'Json'; + $fakeObject->context = 'test'; + + $result = $this->serializer->serialize($fakeObject); + + static::assertJson($result); + } } diff --git a/tests/Transformer/GeneratorTransformerTest.php b/tests/Transformer/GeneratorTransformerTest.php index c5888fa..af60173 100644 --- a/tests/Transformer/GeneratorTransformerTest.php +++ b/tests/Transformer/GeneratorTransformerTest.php @@ -7,10 +7,16 @@ use Blacktrs\DataTransformer\Tests\Fake\Item\FakeSimpleObject; use Blacktrs\DataTransformer\Transformer\GeneratorTransformer; use PHPUnit\Framework\TestCase; -use Generator; class GeneratorTransformerTest extends TestCase { + private GeneratorTransformer $transformer; + + protected function setUp(): void + { + $this->transformer = new GeneratorTransformer(); + } + public function testTransform(): void { $data = [ @@ -18,9 +24,8 @@ public function testTransform(): void ['id' => 20000, 'label' => 'Second Label'], ]; - $transformer = new GeneratorTransformer(); - $collection = $transformer->transform(FakeSimpleObject::class, $data); + $collection = $this->transformer->transform(FakeSimpleObject::class, $data); - self::assertInstanceOf(Generator::class, $collection); + static::assertContainsOnlyInstancesOf(FakeSimpleObject::class, $collection); } } diff --git a/tests/Transformer/ObjectTransformerTest.php b/tests/Transformer/ObjectTransformerTest.php index fddfa06..9a4c3e9 100755 --- a/tests/Transformer/ObjectTransformerTest.php +++ b/tests/Transformer/ObjectTransformerTest.php @@ -4,6 +4,7 @@ namespace Blacktrs\DataTransformer\Tests\Transformer; +use Blacktrs\DataTransformer\Transformer\{Transformer, TransformerException}; use Blacktrs\DataTransformer\Tests\Fake\Item\{FakeObjectWithConstructorAndGetters, FakeObjectWithEnumProperty, FakeObjectWithFieldNameDeclaration, @@ -11,8 +12,8 @@ FakeObjectWithOtherItem, FakeObjectWithPrivateProperties, FakeSimpleObject, - FakeSimpleObjectWithConstructor}; -use Blacktrs\DataTransformer\Transformer\Transformer; + FakeSimpleObjectWithConstructor +}; use PHPUnit\Framework\TestCase; use DateTime; @@ -27,7 +28,9 @@ public function setUp(): void public function testSimpleTransformation(): void { - $data = ['id' => 2, 'label' => 'Test Label', 'description' => 'some text', 'context' => null]; + $bytes = random_bytes(10); + + $data = ['id' => 2, 'label' => 'Test Label', 'description' => 'some text', 'context' => null, 'unknownType' => $bytes]; /** @var FakeSimpleObject $fakeObject */ $fakeObject = $this->transformer->transform(FakeSimpleObject::class, $data); @@ -36,6 +39,7 @@ public function testSimpleTransformation(): void self::assertSame($data['label'], $fakeObject->label); self::assertNotSame($data['description'], $fakeObject->description); self::assertNull($fakeObject->context); + self::assertSame($bytes, $fakeObject->unknownType); } public function testCustomFieldNameDeclaration(): void @@ -102,6 +106,14 @@ public function testEnumPropertyTransformation(): void self::assertSame($fakeObject->size->name, $data['size']); } + public function testUnknownEnumPropertyTransformation(): void + { + $data = ['id' => 1, 'name' => 'John Doe', 'color' => 'green', 'size' => 'XXL']; + + $this->expectException(TransformerException::class); + $this->transformer->transform(FakeObjectWithEnumProperty::class, $data); + } + public function testObjectWithPromotedProperties(): void { $data = ['id' => 1000, 'label' => 'Constructor promotion']; From f73a71407dad4ca4b0e564494271fbcbe47aa102 Mon Sep 17 00:00:00 2001 From: Taras Chornyi Date: Sun, 17 Sep 2023 15:25:52 +0300 Subject: [PATCH 2/6] - fix lock file --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 6075d1f..0dd282b 100755 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7c17dfdd4799d755f4cf96b940b941f6", + "content-hash": "3043e4ab6bd41289cbf6e27c30e5265e", "packages": [], "packages-dev": [ { From 379ac1c0b066e95141273bbbf8d8b8346682ff43 Mon Sep 17 00:00:00 2001 From: Taras Chornyi Date: Sun, 17 Sep 2023 15:33:19 +0300 Subject: [PATCH 3/6] - remove version from composer --- composer.json | 1 - composer.lock | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e8faa95..946c941 100755 --- a/composer.json +++ b/composer.json @@ -1,7 +1,6 @@ { "name": "blacktrs/data-transformer", "type": "library", - "version": "1.1.1", "description": "Zero-dependency PHP array-to-object transformer", "require": { "php": ">=8.2" diff --git a/composer.lock b/composer.lock index 0dd282b..b69aeb4 100755 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3043e4ab6bd41289cbf6e27c30e5265e", + "content-hash": "42f7a8bdc3e68b802f49a4588b96a195", "packages": [], "packages-dev": [ { From 1d24e2751f00d211f2e9d9741714313214e2d48b Mon Sep 17 00:00:00 2001 From: Taras Chornyi Date: Sun, 17 Sep 2023 15:35:50 +0300 Subject: [PATCH 4/6] Use PHP 8.2 for workflow --- .github/workflows/php.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml index d57ce0f..d6078fd 100644 --- a/.github/workflows/php.yaml +++ b/.github/workflows/php.yaml @@ -17,6 +17,11 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + - name: Validate composer.json and composer.lock run: composer validate --strict From 6dbf8661d38de4dd1954331eebc11c9f1953ab49 Mon Sep 17 00:00:00 2001 From: Taras Chornyi Date: Sun, 17 Sep 2023 16:09:59 +0300 Subject: [PATCH 5/6] Allow workflow to write contents --- .github/workflows/php.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/php.yaml b/.github/workflows/php.yaml index d6078fd..49082ee 100644 --- a/.github/workflows/php.yaml +++ b/.github/workflows/php.yaml @@ -7,7 +7,7 @@ on: branches: [ "main" ] permissions: - contents: read + contents: write jobs: build: From 851dc40a39c84cd9b21b5e8ee65e43eb4fa67274 Mon Sep 17 00:00:00 2001 From: Taras Chornyi Date: Sun, 17 Sep 2023 16:21:32 +0300 Subject: [PATCH 6/6] Add coverage badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2020330..231739b 100755 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +![Coverage](https://raw.githubusercontent.com/blacktrs/data-transformer/image-data/coverage.svg) + # Data transformer The modern PHP library for converting arrays into objects and vice-versa.