From 3fbd7bdb28f4052286e5d54348ebdc3e9ce3bdf5 Mon Sep 17 00:00:00 2001 From: Christian Kolb Date: Thu, 5 Dec 2024 13:41:07 +0100 Subject: [PATCH] Switch to self aware normalizers (#74) * Switch to self aware normalizers * Update readme * Improve readme * Remove import * Fix imported type * Update to latest self aware normalizers * Update changelog * Fixed naming in readme --------- Co-authored-by: Christian Kolb --- .docker/{php-8.2 => php-8.4}/Dockerfile | 4 +- .../docker-php-ext-xdebug.ini | 0 .docker/{php-8.2 => php-8.4}/php-fpm.conf | 0 .docker/{php-8.2 => php-8.4}/php.ini | 0 .github/workflows/ci.yml | 40 +++--- CHANGELOG.md | 7 + Makefile | 92 ++++--------- README.md | 22 ++-- UPGRADE.md | 59 +++++++++ composer.json | 11 +- composer.lock | 56 +++++++- docker-compose.yml | 8 +- phpstan.neon | 2 - src/DependencyInjection/IdsExtension.php | 9 +- src/Doctrine/IdListType.php | 72 ----------- src/Doctrine/IdType.php | 46 +------ src/IdsBundle.php | 4 +- src/Resources/config/services.yaml | 7 - src/Serializer/IdListNormalizer.php | 80 ------------ src/Serializer/IdNormalizer.php | 62 --------- src/ValueObject/Id.php | 24 +++- src/ValueObject/IdList.php | 31 ++++- tests/Doctrine/IdListTypeTest.php | 53 -------- tests/Doctrine/IdTypeTest.php | 47 ------- tests/Serializer/IdListNormalizerTest.php | 121 ------------------ tests/Serializer/IdNormalizerTest.php | 79 ------------ tests/Test/Doctrine/UserIdListType.php | 10 +- tests/ValueObject/IdListTest.php | 30 +++++ tests/ValueObject/IdTest.php | 18 +++ 29 files changed, 298 insertions(+), 696 deletions(-) rename .docker/{php-8.2 => php-8.4}/Dockerfile (91%) rename .docker/{php-8.2 => php-8.4}/docker-php-ext-xdebug.ini (100%) rename .docker/{php-8.2 => php-8.4}/php-fpm.conf (100%) rename .docker/{php-8.2 => php-8.4}/php.ini (100%) delete mode 100644 src/Doctrine/IdListType.php delete mode 100644 src/Resources/config/services.yaml delete mode 100644 src/Serializer/IdListNormalizer.php delete mode 100644 src/Serializer/IdNormalizer.php delete mode 100644 tests/Doctrine/IdListTypeTest.php delete mode 100644 tests/Doctrine/IdTypeTest.php delete mode 100644 tests/Serializer/IdListNormalizerTest.php delete mode 100644 tests/Serializer/IdNormalizerTest.php diff --git a/.docker/php-8.2/Dockerfile b/.docker/php-8.4/Dockerfile similarity index 91% rename from .docker/php-8.2/Dockerfile rename to .docker/php-8.4/Dockerfile index 7736432..46697ff 100644 --- a/.docker/php-8.2/Dockerfile +++ b/.docker/php-8.4/Dockerfile @@ -1,7 +1,7 @@ # Build with -# docker buildx build --platform linux/arm64,linux/amd64 -t ghcr.io/digital-craftsman-de/ids-php-8.2:latest ./.docker/php-8.2 --push +# docker buildx build --platform linux/arm64,linux/amd64 -t ghcr.io/digital-craftsman-de/ids-php-8.4:latest ./.docker/php-8.4 --push -FROM php:8.2-fpm-alpine +FROM php:8.4-fpm-alpine RUN apk add --update \ autoconf \ diff --git a/.docker/php-8.2/docker-php-ext-xdebug.ini b/.docker/php-8.4/docker-php-ext-xdebug.ini similarity index 100% rename from .docker/php-8.2/docker-php-ext-xdebug.ini rename to .docker/php-8.4/docker-php-ext-xdebug.ini diff --git a/.docker/php-8.2/php-fpm.conf b/.docker/php-8.4/php-fpm.conf similarity index 100% rename from .docker/php-8.2/php-fpm.conf rename to .docker/php-8.4/php-fpm.conf diff --git a/.docker/php-8.2/php.ini b/.docker/php-8.4/php.ini similarity index 100% rename from .docker/php-8.2/php.ini rename to .docker/php-8.4/php.ini diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee405c9..aeba602 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,8 +2,8 @@ name: CI on: push jobs: - test-8-2: - name: "Tests on PHP 8.2" + test-8-3: + name: "Tests on PHP 8.3" runs-on: ubuntu-latest steps: @@ -19,25 +19,25 @@ jobs: - name: Setup env run: cp .github/workflows/.env .env - - name: Cache PHP 8.2 dependencies - id: cache-php-8-2-dependencies + - name: Cache PHP 8.3 dependencies + id: cache-php-8-3-dependencies uses: actions/cache@v3 env: - cache-name: cache-php-8-2-dependencies + cache-name: cache-php-8-3-dependencies with: path: | ~/.cache vendor - key: ${{ runner.os }}-php-8-2-cache-${{ hashFiles('composer.lock') }} + key: ${{ runner.os }}-php-8-3-cache-${{ hashFiles('composer.lock') }} restore-keys: | - ${{ runner.os }}-php-8-2-cache-${{ hashFiles('composer.lock') }} + ${{ runner.os }}-php-8-3-cache-${{ hashFiles('composer.lock') }} - name: Install - if: steps.cache-php-8-2-dependencies.outputs.cache-hit != 'true' - run: make install-8.2 + if: steps.cache-php-8-3-dependencies.outputs.cache-hit != 'true' + run: make install-8.3 - name: Run PHP tests - run: make php-8.2-tests-ci + run: make php-8.3-tests-ci - name: Upload to Codecov uses: codecov/codecov-action@v2 @@ -46,8 +46,8 @@ jobs: files: ./coverage.xml verbose: true - test-8-3: - name: "Tests on PHP 8.3" + test-8-4: + name: "Tests on PHP 8.4" runs-on: ubuntu-latest steps: @@ -63,25 +63,25 @@ jobs: - name: Setup env run: cp .github/workflows/.env .env - - name: Cache PHP 8.3 dependencies - id: cache-php-8-3-dependencies + - name: Cache PHP 8.4 dependencies + id: cache-php-8-4-dependencies uses: actions/cache@v3 env: - cache-name: cache-php-8-3-dependencies + cache-name: cache-php-8-4-dependencies with: path: | ~/.cache vendor - key: ${{ runner.os }}-php-8-3-cache-${{ hashFiles('composer.lock') }} + key: ${{ runner.os }}-php-8-4-cache-${{ hashFiles('composer.lock') }} restore-keys: | - ${{ runner.os }}-php-8-3-cache-${{ hashFiles('composer.lock') }} + ${{ runner.os }}-php-8-4-cache-${{ hashFiles('composer.lock') }} - name: Install - if: steps.cache-php-8-3-dependencies.outputs.cache-hit != 'true' - run: make install-8.3 + if: steps.cache-php-8-4-dependencies.outputs.cache-hit != 'true' + run: make install-8.4 - name: Run PHP tests - run: make php-8.3-tests-ci + run: make php-8.4-tests-ci mutation-testing: name: "Mutation tests" diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b903c2..1d06355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2.0.0 + +- **[Breaking change](./UPGRADE.md#switched-away-from-custom-normalizers)**: Switched away from custom normalizers to the normalizers from `digital-craftsman/self-aware-normalizers`. +- **[Breaking change](./UPGRADE.md#removed-custom-doctrine-type-for-idlist)**: Removed base doctrine type `IdListType` in favor of `ArrayNormalizableType` of `digital-craftsman/self-aware-normalizers`. +- **[Breaking change](./UPGRADE.md#upgrade-to-at-least-php-83)**: Dropped support for PHP 8.2. +- Added support for PHP 8.4. + ## 1.4.0 - Added method `toString(): string` to `Id`. diff --git a/Makefile b/Makefile index bd10704..16b2701 100644 --- a/Makefile +++ b/Makefile @@ -4,147 +4,111 @@ uid = $$(id -u) gid = $$(id -g) pwd = $$(pwd) -default: help - -## -## Help -## ---- -## - -## help Print commands help. -.PHONY: help - -help: Makefile - @sed -n 's/^##//p' $< - ## ## Docker ## ------ ## -## build Build the Docker images. .PHONY: build build: docker compose build -## up Start the Docker stack. .PHONY: up up: .up .up: docker compose up -d -## down Stop the Docker stack. .PHONY: down down: .down .down: docker compose down -## update Rebuild Docker images and start stack. .PHONY: update update: build up -## reset Teardown stack, install and start. .PHONY: reset reset: .reset .PHONY: .reset .reset: .down .install .up -## install Install PHP dependencies with the default PHP version (8.3). .PHONY: .install -install: install-8.3 +install: install-8.4 -## install-8.2 Install PHP dependencies with PHP 8.2. -.PHONY: install-8.2 -install-8.2: - docker compose run --rm php-8.2 composer install - -## install-8.3 Install PHP dependencies with PHP 8.3. .PHONY: install-8.3 install-8.3: docker compose run --rm php-8.3 composer install -## php-cli Enter a shell for the default PHP version (8.3). +.PHONY: install-8.4 +install-8.4: + docker compose run --rm php-8.4 composer install + .PHONY: php-cli php-cli: php-8.3-cli -## php-8.2-cli Enter a shell for PHP 8.2. -.PHONY: php-8.2-cli -php-8.2-cli: - docker compose run --rm php-8.2 sh - -## php-8.3-cli Enter a shell for PHP 8.3. .PHONY: php-8.3-cli php-8.3-cli: docker compose run --rm php-8.3 sh +.PHONY: php-8.4-cli +php-8.4-cli: + docker compose run --rm php-8.4 sh + ## ## Tests and code validation ## ------------------------- ## -## verify Run all validations and tests. .PHONY: verify verify: php-code-validation php-tests php-mutation-testing -## php-tests Run the tests for all relevant PHP versions. .PHONY: php-tests -php-tests: php-8.2-tests php-8.3-tests +php-tests: php-8.3-tests php-8.4-tests -## php-tests-coverage Run the tests for all relevant PHP versions including coverage report as HTML. .PHONY: php-tests-coverage -php-tests-coverage: php-8.3-tests-html-coverage +php-tests-coverage: php-8.4-tests-html-coverage -## php-8.2-tests Run tests with PHP 8.2. -.PHONY: php-8.2-tests -php-8.2-tests: - docker compose run --rm php-8.2 ./vendor/bin/phpunit - -## php-8.3-tests Run tests with PHP 8.3. .PHONY: php-8.3-tests php-8.3-tests: docker compose run --rm php-8.3 ./vendor/bin/phpunit -## php-8.2-tests-html-coverage Run the tests with PHP 8.2 including coverage report as HTML. -.PHONY: php-8.2-tests-html-coverage -php-8.2-tests-html-coverage: - docker compose run --rm php-8.2 ./vendor/bin/phpunit --coverage-html ./coverage +.PHONY: php-8.4-tests +php-8.4-tests: + docker compose run --rm php-8.4 ./vendor/bin/phpunit -## php-8.3-tests-html-coverage Run the tests with PHP 8.3 including coverage report as HTML. .PHONY: php-8.3-tests-html-coverage php-8.3-tests-html-coverage: docker compose run --rm php-8.3 ./vendor/bin/phpunit --coverage-html ./coverage -## php-code-validation Run code fixers and linters with default PHP version (8.2). +.PHONY: php-8.4-tests-html-coverage +php-8.4-tests-html-coverage: + docker compose run --rm php-8.4 ./vendor/bin/phpunit --coverage-html ./coverage + .PHONY: php-code-validation php-code-validation: - docker compose run --rm php-8.2 ./vendor/bin/php-cs-fixer fix - docker compose run --rm php-8.2 ./vendor/bin/psalm --show-info=false --no-diff - docker compose run --rm php-8.2 ./vendor/bin/phpstan --xdebug + docker compose run --rm php-8.3 ./vendor/bin/php-cs-fixer fix + docker compose run --rm php-8.3 ./vendor/bin/psalm --show-info=false --no-diff + docker compose run --rm php-8.3 ./vendor/bin/phpstan --xdebug -## php-mutation-testing Run mutation testing with default PHP version (8.2). .PHONY: php-mutation-testing php-mutation-testing: - docker compose run --rm php-8.2 ./vendor/bin/infection --show-mutations --only-covered --threads=8 + docker compose run --rm php-8.3 ./vendor/bin/infection --show-mutations --only-covered --threads=8 ## ## CI ## -- ## -## php-8.2-tests-ci Run the tests for PHP 8.2 for CI. -.PHONY: php-8.2-tests-ci -php-8.2-tests-ci: - docker compose run --rm php-8.2 ./vendor/bin/phpunit --coverage-clover ./coverage.xml - -## php-8.3-tests-ci Run the tests for PHP 8.3 for CI. .PHONY: php-8.3-tests-ci php-8.3-tests-ci: - docker compose run --rm php-8.3 ./vendor/bin/phpunit + docker compose run --rm php-8.3 ./vendor/bin/phpunit --coverage-clover ./coverage.xml + +.PHONY: php-8.4-tests-ci +php-8.4-tests-ci: + docker compose run --rm php-8.4 ./vendor/bin/phpunit -## php-mutation-testing-ci Run mutation testing for CI. .PHONY: php-mutation-testing-ci php-mutation-testing-ci: - docker compose run --rm php-8.2 ./vendor/bin/infection --only-covered --threads=max + docker compose run --rm php-8.3 ./vendor/bin/infection --only-covered --threads=max diff --git a/README.md b/README.md index 8982a9d..cd25178 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ A Symfony bundle to work with id and id list value objects in Symfony. It includ As it's a central part of an application, it's tested thoroughly (including mutation testing). -[![Latest Stable Version](https://img.shields.io/badge/stable-1.4.0-blue)](https://packagist.org/packages/digital-craftsman/ids) -[![PHP Version Require](https://img.shields.io/badge/php-8.2|8.3-5b5d95)](https://packagist.org/packages/digital-craftsman/ids) +[![Latest Stable Version](https://img.shields.io/badge/stable-2.0.0-blue)](https://packagist.org/packages/digital-craftsman/ids) +[![PHP Version Require](https://img.shields.io/badge/php-8.3|8.4-5b5d95)](https://packagist.org/packages/digital-craftsman/ids) [![codecov](https://codecov.io/gh/digital-craftsman-de/ids/branch/main/graph/badge.svg?token=BL0JKZYLBG)](https://codecov.io/gh/digital-craftsman-de/ids) ![Packagist Downloads](https://img.shields.io/packagist/dt/digital-craftsman/ids) ![Packagist License](https://img.shields.io/packagist/l/digital-craftsman/ids) @@ -67,7 +67,7 @@ $requestingUser->userId->mustNotBeEqualTo( ### Symfony serializer -If you're injecting the `SerializerInterface` directly, there is nothing to do. The normalizer for the id is automatically registered. +If you're injecting the `SerializerInterface` directly, there is nothing to do. The normalizer from [`digital-craftsman/self-aware-normalizers`](https://github.com/digital-craftsman-de/self-aware-normalizers) are registered automatically and will handle the serialization and deserialization of the `Id` class, as it implements the `StringNormalizable` interface. ```php namespace App\DTO; @@ -103,7 +103,7 @@ public function handle(UserPayload $userPayload): string } ``` -This can be combined with the [CQRS bundle](https://github.com/digital-craftsman-de/cqrs) to have serialized ids there. +This can be combined with the [CQS bundle](https://github.com/digital-craftsman-de/cqs-routing) to have serialized ids there. ### Doctrine types @@ -226,7 +226,7 @@ $idsOfEnabledUsers->mustContainId( ### Symfony serializer -If you're injecting the `SerializerInterface` directly, there is nothing to do. The normalizer for the id list is automatically registered. +If you're injecting the `SerializerInterface` directly, there is nothing to do. The normalizer from [`digital-craftsman/self-aware-normalizers`](https://github.com/digital-craftsman-de/self-aware-normalizers) are registered automatically and will handle the serialization and deserialization of the `IdList` class, as it implements the `ArrayNormalizable` interface. ### Doctrine types @@ -239,26 +239,20 @@ declare(strict_types=1); namespace App\Doctrine; -use App\ValueObject\UserId; use App\ValueObject\UserIdList; -use DigitalCraftsman\Ids\Doctrine\IdListType; +use DigitalCraftsman\SelfAwareNormalizers\Doctrine\ArrayNormalizableType; -final class UserIdListType extends IdListType +final class UserIdListType extends ArrayNormalizableType { protected function getTypeName(): string { return 'user_id_list'; } - protected function getIdListClass(): string + protected function getClass(): string { return UserIdList::class; } - - protected function getIdClass(): string - { - return UserId::class; - } } ``` diff --git a/UPGRADE.md b/UPGRADE.md index 3745a6a..6f5ed0d 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -1,5 +1,64 @@ # Upgrade guide +## From 1.4.* to 2.0.0 + +### Switched away from custom normalizers + +Switched away from custom normalizers to the normalizers from `digital-craftsman/self-aware-normalizers`. + +The new normalizers are automatically registered and will handle the normalization of ids and id lists. When you've configured the `IdNormalizer` or `IdListNormalizer` manually somewhere, you need to replace them with the `StringNormalizableNormalizer` and `ArrayNormalizableNormalizer` respectively. + +The `Id` and `IdList` classes now contain a `normalize` and `denormalize` methods. So if you've implemented those methods in your classes, you need to rename yours to something else. + +### Removed custom doctrine type for IdList + +There is no need for the custom doctrine type for `IdList` anymore. Instead, extend your doctrine types from `ArrayNormalizableType`. As the id list knows which id class to construct, the doctrine type doesn't need the `getIdClass` method anymore. + +```php +use DigitalCraftsman\Ids\Doctrine\IdListType; + +final class UserIdListType extends IdListType +{ + public static function getTypeName(): string + { + return 'user_id_list'; + } + + public static function getClass(): string + { + return UserIdList::class; + } + + public static function getIdClass(): string + { + return UserId::class; + } +} +``` + +After: + +```php +use DigitalCraftsman\SelfAwareNormalizers\Doctrine\ArrayNormalizableType; + +final class UserIdListType extends ArrayNormalizableType +{ + public static function getTypeName(): string + { + return 'user_id_list'; + } + + public static function getClass(): string + { + return UserIdList::class; + } +} +``` + +### Upgrade to at least PHP 8.3 + +Support for PHP 8.2 was dropped, so you have to upgrade to at least PHP 8.3. + ## From 1.3.* to 1.4.0 Nothing to do. diff --git a/composer.json b/composer.json index 184769d..57a0700 100644 --- a/composer.json +++ b/composer.json @@ -3,11 +3,12 @@ "description": "Working with value objects for ids in Symfony", "type": "symfony-bundle", "require": { - "php": "8.2.*|8.3.*", - "symfony/framework-bundle": "^6.3|^7.0", - "symfony/serializer": "^6.3|^7.0", - "doctrine/dbal": "^2.13.8|^3.3.6", - "symfony/polyfill-uuid": "^1.26" + "php": "8.3.*|8.4.*", + "symfony/framework-bundle": "^7.0", + "symfony/serializer": "^7.0", + "doctrine/dbal": "^3.3.6", + "symfony/polyfill-uuid": "^1.26", + "digital-craftsman/self-aware-normalizers": "^0.2.0" }, "require-dev": { "vimeo/psalm": "^5.25", diff --git a/composer.lock b/composer.lock index bf598a7..e666a13 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,60 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b6f51eb6d4b97dc65d21419fd6d9ca8c", + "content-hash": "8cc08f8de9a90307656d2899880f2f2c", "packages": [ + { + "name": "digital-craftsman/self-aware-normalizers", + "version": "v0.2.0", + "source": { + "type": "git", + "url": "https://github.com/digital-craftsman-de/self-aware-normalizers.git", + "reference": "4ba3f01ddebba83ef77ba9e77c8183bd43f81154" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/digital-craftsman-de/self-aware-normalizers/zipball/4ba3f01ddebba83ef77ba9e77c8183bd43f81154", + "reference": "4ba3f01ddebba83ef77ba9e77c8183bd43f81154", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^3.3.6", + "php": "8.3.*|8.4.*", + "symfony/framework-bundle": "^7.1", + "symfony/serializer": "^7.1" + }, + "require-dev": { + "digital-craftsman/ids": "^1.3", + "friendsofphp/php-cs-fixer": "^v3.59.3", + "infection/infection": "0.27.*", + "phpunit/phpunit": "^10.5", + "symfony/property-access": "^7.1", + "symfony/property-info": "^7.1", + "vimeo/psalm": "^5.25" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "DigitalCraftsman\\SelfAwareNormalizers\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Kolb", + "email": "info@digital-craftsman.de" + } + ], + "description": "Adds interfaces and normalizers that allow value objects to normalize and denormalize themselves.", + "support": { + "issues": "https://github.com/digital-craftsman-de/self-aware-normalizers/issues", + "source": "https://github.com/digital-craftsman-de/self-aware-normalizers/tree/v0.2.0" + }, + "time": "2024-12-05T12:13:12+00:00" + }, { "name": "doctrine/cache", "version": "2.2.0", @@ -7408,7 +7460,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "8.2.*|8.3.*" + "php": "8.3.*|8.4.*" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/docker-compose.yml b/docker-compose.yml index 913ac1e..a0fa248 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: - php-8.2: - image: ghcr.io/digital-craftsman-de/ids-php-8.2 + php-8.3: + image: ghcr.io/digital-craftsman-de/ids-php-8.3 env_file: - .env volumes: @@ -9,8 +9,8 @@ services: extra_hosts: - "host.docker.internal:host-gateway" - php-8.3: - image: ghcr.io/digital-craftsman-de/ids-php-8.3 + php-8.4: + image: ghcr.io/digital-craftsman-de/ids-php-8.4 env_file: - .env volumes: diff --git a/phpstan.neon b/phpstan.neon index 19b36a6..99937a3 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,9 +3,7 @@ parameters: paths: - src - tests - checkGenericClassInNonGenericObjectType: false checkExplicitMixed: false excludePaths: # The @template annotations are clashing with the ones of Psalm - src/ValueObject/IdList.php - - src/ValueObject/OrderedIdList.php diff --git a/src/DependencyInjection/IdsExtension.php b/src/DependencyInjection/IdsExtension.php index 29e86e5..bc9330e 100644 --- a/src/DependencyInjection/IdsExtension.php +++ b/src/DependencyInjection/IdsExtension.php @@ -4,18 +4,15 @@ namespace DigitalCraftsman\Ids\DependencyInjection; -use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\Extension; -use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -/** @codeCoverageIgnore */ +/** + * @codeCoverageIgnore + */ final class IdsExtension extends Extension { - /** @param array $configs */ public function load(array $configs, ContainerBuilder $container): void { - $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); - $loader->load('services.yaml'); } } diff --git a/src/Doctrine/IdListType.php b/src/Doctrine/IdListType.php deleted file mode 100644 index 43d8a4d..0000000 --- a/src/Doctrine/IdListType.php +++ /dev/null @@ -1,72 +0,0 @@ - */ - abstract public static function getClass(): string; - - /** @psalm-return class-string */ - abstract public static function getIdClass(): string; - - /** @codeCoverageIgnore */ - public function getSQLDeclaration(array $column, AbstractPlatform $platform): string - { - $column['jsonb'] = true; - - return $platform->getJsonTypeDeclarationSQL($column); - } - - /** @param IdList|null $value */ - public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string - { - if ($value === null) { - return null; - } - - return json_encode($value->idsAsStringList(), JSON_THROW_ON_ERROR); - } - - /** @param ?string $value */ - public function convertToPHPValue($value, AbstractPlatform $platform): ?object - { - if ($value === null) { - return null; - } - - $idListClass = static::getClass(); - $idClass = static::getIdClass(); - - /** @var array $idStrings */ - $idStrings = json_decode($value, true, 512, JSON_THROW_ON_ERROR); - - $ids = []; - foreach ($idStrings as $idString) { - $ids[] = new $idClass($idString); - } - - return new $idListClass($ids); - } - - /** @codeCoverageIgnore */ - public function getName(): string - { - return static::getTypeName(); - } - - /** @codeCoverageIgnore */ - public function requiresSQLCommentHint(AbstractPlatform $platform): bool - { - return true; - } -} diff --git a/src/Doctrine/IdType.php b/src/Doctrine/IdType.php index 9d9d012..305d438 100644 --- a/src/Doctrine/IdType.php +++ b/src/Doctrine/IdType.php @@ -4,52 +4,16 @@ namespace DigitalCraftsman\Ids\Doctrine; -use DigitalCraftsman\Ids\ValueObject\Id; +use DigitalCraftsman\SelfAwareNormalizers\Doctrine\StringNormalizableType; use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\Type; -abstract class IdType extends Type +abstract class IdType extends StringNormalizableType { - abstract public static function getTypeName(): string; - - /** @return class-string */ - abstract public static function getClass(): string; - - /** @codeCoverageIgnore */ + /** + * @codeCoverageIgnore + */ public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { return $platform->getGuidTypeDeclarationSQL($column); } - - /** @param ?Id $value */ - public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string - { - if ($value === null) { - return null; - } - - return (string) $value; - } - - /** @param ?string $value */ - public function convertToPHPValue($value, AbstractPlatform $platform): ?Id - { - if ($value === null) { - return null; - } - - return static::getClass()::fromString($value); - } - - /** @codeCoverageIgnore */ - public function getName(): string - { - return static::getTypeName(); - } - - /** @codeCoverageIgnore */ - public function requiresSQLCommentHint(AbstractPlatform $platform): bool - { - return true; - } } diff --git a/src/IdsBundle.php b/src/IdsBundle.php index 27fb4d4..d92ed89 100644 --- a/src/IdsBundle.php +++ b/src/IdsBundle.php @@ -4,7 +4,9 @@ use Symfony\Component\HttpKernel\Bundle\Bundle; -/** @codeCoverageIgnore */ +/** + * @codeCoverageIgnore + */ final class IdsBundle extends Bundle { } diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml deleted file mode 100644 index 4551bdf..0000000 --- a/src/Resources/config/services.yaml +++ /dev/null @@ -1,7 +0,0 @@ -services: - - DigitalCraftsman\Ids\Serializer\IdNormalizer: - tags: [ { name: 'serializer.normalizer' } ] - - DigitalCraftsman\Ids\Serializer\IdListNormalizer: - tags: [ { name: 'serializer.normalizer' } ] diff --git a/src/Serializer/IdListNormalizer.php b/src/Serializer/IdListNormalizer.php deleted file mode 100644 index 6e64de0..0000000 --- a/src/Serializer/IdListNormalizer.php +++ /dev/null @@ -1,80 +0,0 @@ - $context - */ - public function supportsNormalization($data, $format = null, array $context = []): bool - { - return $data instanceof IdList; - } - - /** - * @param string $type - * @param array $context - */ - public function supportsDenormalization($data, $type, $format = null, array $context = []): bool - { - if (!class_exists($type)) { - return false; - } - - $parentClass = get_parent_class($type); - - return $parentClass === IdList::class; - } - - /** - * @param IdList $data - * @param array $context - * - * @return array - */ - public function normalize($data, $format = null, array $context = []): array - { - return $data->idsAsStringList(); - } - - /** - * @param ?array $data - * @param class-string $type - * @param array $context - */ - public function denormalize($data, $type, $format = null, array $context = []): ?IdList - { - if ($data === null) { - return null; - } - - $idClass = $type::handlesIdClass(); - - $ids = []; - foreach ($data as $string) { - $ids[] = new $idClass($string); - } - - return new $type($ids); - } - - /** - * @return array - * - * @codeCoverageIgnore - */ - public function getSupportedTypes(?string $format): array - { - return [ - IdList::class => true, - ]; - } -} diff --git a/src/Serializer/IdNormalizer.php b/src/Serializer/IdNormalizer.php deleted file mode 100644 index 41984a9..0000000 --- a/src/Serializer/IdNormalizer.php +++ /dev/null @@ -1,62 +0,0 @@ - $context */ - public function supportsNormalization($data, $format = null, array $context = []): bool - { - return $data instanceof Id; - } - - /** - * @param string $type - * @param array $context - */ - public function supportsDenormalization($data, $type, $format = null, array $context = []): bool - { - return is_subclass_of($type, Id::class); - } - - /** - * @param Id $data - * @param array $context - */ - public function normalize($data, $format = null, array $context = []): string - { - return (string) $data; - } - - /** - * @param ?string $data - * @param class-string $type - * @param array $context - */ - public function denormalize($data, $type, $format = null, array $context = []): ?Id - { - if ($data === null) { - return null; - } - - return $type::fromString($data); - } - - /** - * @return array - * - * @codeCoverageIgnore - */ - public function getSupportedTypes(?string $format): array - { - return [ - Id::class => true, - ]; - } -} diff --git a/src/ValueObject/Id.php b/src/ValueObject/Id.php index 817f8a7..55f5c14 100644 --- a/src/ValueObject/Id.php +++ b/src/ValueObject/Id.php @@ -4,9 +4,11 @@ namespace DigitalCraftsman\Ids\ValueObject; -abstract readonly class Id implements \Stringable +use DigitalCraftsman\SelfAwareNormalizers\Serializer\StringNormalizable; + +abstract readonly class Id implements \Stringable, StringNormalizable { - // Construction + // -- Construction final public function __construct( public string $value, @@ -26,14 +28,26 @@ public static function fromString(string $id): static return new static($id); } - // Magic + // -- String normalizable + + public static function denormalize(string $data): static + { + return new static($data); + } + + public function normalize(): string + { + return $this->value; + } + + // -- Magic public function __toString(): string { return $this->value; } - // Accessors + // -- Accessors public function toString(): string { @@ -56,7 +70,7 @@ public function isNotEqualTo(self $id): bool return $this->value !== $id->value; } - // Guards + // -- Guards /** * @param static $id diff --git a/src/ValueObject/IdList.php b/src/ValueObject/IdList.php index 5f0f31d..e2e2f12 100644 --- a/src/ValueObject/IdList.php +++ b/src/ValueObject/IdList.php @@ -4,6 +4,8 @@ namespace DigitalCraftsman\Ids\ValueObject; +use DigitalCraftsman\SelfAwareNormalizers\Serializer\ArrayNormalizable; + /** * @template T extends Id * @@ -15,8 +17,10 @@ * didn't find a solution without suppressing this psalm check. * * @psalm-suppress UnsafeGenericInstantiation + * + * @psalm-type NormalizedIdList = list */ -abstract readonly class IdList implements \IteratorAggregate, \Countable +abstract readonly class IdList implements \IteratorAggregate, \Countable, ArrayNormalizable { /** * @var array @@ -123,6 +127,31 @@ final public static function fromIdLists(array $idLists): static */ abstract public static function handlesIdClass(): string; + // -- Array normalizable + + /** + * @param NormalizedIdList $data + */ + public static function denormalize(array $data): static + { + $idClass = static::handlesIdClass(); + + $ids = []; + foreach ($data as $idString) { + $ids[] = new $idClass($idString); + } + + return new static($ids); + } + + /** + * @return NormalizedIdList + */ + public function normalize(): array + { + return $this->idsAsStringList(); + } + // -- Transformers /** diff --git a/tests/Doctrine/IdListTypeTest.php b/tests/Doctrine/IdListTypeTest.php deleted file mode 100644 index 4427d87..0000000 --- a/tests/Doctrine/IdListTypeTest.php +++ /dev/null @@ -1,53 +0,0 @@ -convertToDatabaseValue($userIdList, $platform); - $phpValue = $userIdListType->convertToPHPValue($databaseValue, $platform); - - // -- Assert - self::assertEquals($userIdList, $phpValue); - } - - #[Test] - public function convert_from_and_to_value_value_works(): void - { - // -- Arrange - $userIdListType = new UserIdListType(); - $platform = new PostgreSQLPlatform(); - - // -- Act - $databaseValue = $userIdListType->convertToDatabaseValue(null, $platform); - $phpValue = $userIdListType->convertToPHPValue($databaseValue, $platform); - - // -- Assert - self::assertNull($phpValue); - } -} diff --git a/tests/Doctrine/IdTypeTest.php b/tests/Doctrine/IdTypeTest.php deleted file mode 100644 index f720ed0..0000000 --- a/tests/Doctrine/IdTypeTest.php +++ /dev/null @@ -1,47 +0,0 @@ -convertToDatabaseValue($userId, $platform); - $phpValue = $userIdType->convertToPHPValue($databaseValue, $platform); - - // -- Assert - self::assertEquals($userId, $phpValue); - } - - #[Test] - public function convert_from_and_to_null_value_works(): void - { - // -- Arrange - $userIdType = new UserIdType(); - $platform = new PostgreSQLPlatform(); - - // -- Act - $databaseValue = $userIdType->convertToDatabaseValue(null, $platform); - $phpValue = $userIdType->convertToPHPValue($databaseValue, $platform); - - // -- Assert - self::assertNull($phpValue); - } -} diff --git a/tests/Serializer/IdListNormalizerTest.php b/tests/Serializer/IdListNormalizerTest.php deleted file mode 100644 index b26612d..0000000 --- a/tests/Serializer/IdListNormalizerTest.php +++ /dev/null @@ -1,121 +0,0 @@ -normalize($userIdList); - $denormalizedData = $normalizer->denormalize($normalizedData, UserIdList::class); - - // -- Assert - self::assertEquals($userIdList, $denormalizedData); - } - - #[Test] - public function id_list_denormalization_with_null_works(): void - { - // -- Arrange - $normalizer = new IdListNormalizer(); - - // -- Act - $denormalizedData = $normalizer->denormalize(null, UserIdList::class); - - // -- Assert - self::assertNull($denormalizedData); - } - - #[Test] - public function supports_normalization_for_list(): void - { - // -- Arrange - $userIdList = new UserIdList([]); - - $normalizer = new IdListNormalizer(); - - // -- Act & Assert - self::assertTrue($normalizer->supportsNormalization($userIdList)); - } - - #[Test] - public function supports_normalization_fails_with_wrong_data(): void - { - // -- Arrange - $userId = UserId::generateRandom(); - - $normalizer = new IdListNormalizer(); - - // -- Act & Assert - self::assertFalse($normalizer->supportsNormalization($userId)); - } - - #[Test] - public function supports_denormalization_for_id_list(): void - { - // -- Arrange - $idListData = [ - (string) UserId::generateRandom(), - (string) UserId::generateRandom(), - (string) UserId::generateRandom(), - ]; - - $normalizer = new IdListNormalizer(); - - // -- Act & Assert - self::assertTrue($normalizer->supportsDenormalization($idListData, UserIdList::class)); - } - - #[Test] - public function supports_denormalization_with_array_of_ids(): void - { - // -- Arrange - $idListData = [ - (string) UserId::generateRandom(), - (string) UserId::generateRandom(), - (string) UserId::generateRandom(), - ]; - - $normalizer = new IdListNormalizer(); - - // -- Act & Assert - self::assertFalse($normalizer->supportsDenormalization($idListData, sprintf('%s[]', UserId::class))); - } - - #[Test] - public function supports_denormalization_with_wrong_type(): void - { - // -- Arrange - $idListData = [ - (string) UserId::generateRandom(), - (string) UserId::generateRandom(), - (string) UserId::generateRandom(), - ]; - - $normalizer = new IdListNormalizer(); - - // -- Act & Assert - self::assertFalse($normalizer->supportsDenormalization($idListData, UserId::class)); - } -} diff --git a/tests/Serializer/IdNormalizerTest.php b/tests/Serializer/IdNormalizerTest.php deleted file mode 100644 index 6a407ac..0000000 --- a/tests/Serializer/IdNormalizerTest.php +++ /dev/null @@ -1,79 +0,0 @@ -normalize($userId); - $denormalizedData = $normalizer->denormalize($normalizedData, UserId::class); - - // -- Assert - self::assertEquals($userId, $denormalizedData); - } - - #[Test] - public function id_denormalization_with_null_works(): void - { - // -- Arrange - $normalizer = new IdNormalizer(); - - // -- Act - $denormalizedData = $normalizer->denormalize(null, UserId::class); - - // -- Assert - self::assertNull($denormalizedData); - } - - #[Test] - public function supports_normalization(): void - { - // -- Arrange - $userId = UserId::generateRandom(); - - $normalizer = new IdNormalizer(); - - // -- Act & Assert - self::assertTrue($normalizer->supportsNormalization($userId)); - } - - #[Test] - public function supports_normalization_fails_with_invalid_data(): void - { - // -- Arrange - $userId = 5; - - $normalizer = new IdNormalizer(); - - // -- Act & Assert - self::assertFalse($normalizer->supportsNormalization($userId)); - } - - #[Test] - public function supports_denormalization(): void - { - // -- Arrange - $userId = UserId::generateRandom(); - - $normalizer = new IdNormalizer(); - - // -- Act & Assert - self::assertTrue($normalizer->supportsDenormalization((string) $userId, UserId::class)); - } -} diff --git a/tests/Test/Doctrine/UserIdListType.php b/tests/Test/Doctrine/UserIdListType.php index bb6ca07..7c38a2d 100644 --- a/tests/Test/Doctrine/UserIdListType.php +++ b/tests/Test/Doctrine/UserIdListType.php @@ -4,11 +4,10 @@ namespace DigitalCraftsman\Ids\Test\Doctrine; -use DigitalCraftsman\Ids\Doctrine\IdListType; -use DigitalCraftsman\Ids\Test\ValueObject\UserId; use DigitalCraftsman\Ids\Test\ValueObject\UserIdList; +use DigitalCraftsman\SelfAwareNormalizers\Doctrine\ArrayNormalizableType; -final class UserIdListType extends IdListType +final class UserIdListType extends ArrayNormalizableType { public static function getTypeName(): string { @@ -19,9 +18,4 @@ public static function getClass(): string { return UserIdList::class; } - - public static function getIdClass(): string - { - return UserId::class; - } } diff --git a/tests/ValueObject/IdListTest.php b/tests/ValueObject/IdListTest.php index 98a2541..85f003c 100644 --- a/tests/ValueObject/IdListTest.php +++ b/tests/ValueObject/IdListTest.php @@ -272,6 +272,36 @@ public function add_id_when_not_in_list_works(): void self::assertTrue($addedList->containsId($newId)); } + // -- Array normalizable + + #[Test] + public function normalize_and_denormalize_works(): void + { + // -- Arrange + $idOfUserPeter = UserId::generateRandom(); + $idOfUserTony = UserId::generateRandom(); + $idOfUserBruce = UserId::generateRandom(); + + $idList = new UserIdList([ + $idOfUserPeter, + $idOfUserTony, + $idOfUserBruce, + ]); + $data = [ + $idOfUserPeter->normalize(), + $idOfUserTony->normalize(), + $idOfUserBruce->normalize(), + ]; + + // -- Act + $normalized = $idList->normalize(); + $denormalized = UserIdList::denormalize($data); + + // -- Assert + self::assertSame($data, $normalized); + self::assertEquals($idList, $denormalized); + } + // -- Add ids #[Test] diff --git a/tests/ValueObject/IdTest.php b/tests/ValueObject/IdTest.php index e3852a2..1a7b2dd 100644 --- a/tests/ValueObject/IdTest.php +++ b/tests/ValueObject/IdTest.php @@ -46,6 +46,24 @@ public function to_string_works(): void self::assertSame($idString, $id->toString()); } + // -- String normalizable + + #[Test] + public function normalize_and_denormalize_works(): void + { + // -- Arrange + $id = UserId::fromString('f41e0af4-88c4-4d79-9c1a-6e8ea34a956f'); + $data = 'f41e0af4-88c4-4d79-9c1a-6e8ea34a956f'; + + // -- Act + $normalized = $id->normalize(); + $denormalized = UserId::denormalize($data); + + // -- Assert + self::assertSame($data, $normalized); + self::assertEquals($id, $denormalized); + } + #[Test] public function user_id_is_equal(): void {