diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 833574c6..fc25270f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: coverage: none - name: 'Require Doctrine MongoDB dependencies' - run: composer require --no-update --ignore-platform-reqs --dev --no-interaction --ansi "doctrine/mongodb-odm:^2.4" "doctrine/mongodb-odm-bundle:^4.5.1" + run: composer require --no-update --ignore-platform-reqs --dev --no-interaction --ansi "doctrine/mongodb-odm:^2.6" "doctrine/mongodb-odm-bundle:^5.0.0" - name: 'Install dependencies' id: deps @@ -72,12 +72,7 @@ jobs: mongodb: true # Previous Symfony versions - # … - # Previous PHP versions - # … - - # Most recent versions - name: 'Test Symfony 5.4 [Linux, PHP 8.1]' os: 'ubuntu-latest' php: '8.1' @@ -86,43 +81,58 @@ jobs: mongodb: true mysql: true - - name: 'Test Symfony 5.4 [Windows, PHP 8.1]' - os: 'windows-latest' - php: '8.1' - symfony: '5.4.*@dev' + - name: 'Test Symfony 6.4 [Linux, PHP 8.3]' + os: 'ubuntu-latest' + php: '8.3' + symfony: '6.4.*@dev' + allow-unstable: true mongodb: true mysql: true - allow-unstable: true - - name: 'Test Symfony 6.3 [Linux, PHP 8.1]' + - name: 'Test Symfony 7.0 [Linux, PHP 8.3]' os: 'ubuntu-latest' - php: '8.1' - symfony: '6.3.*@dev' - mongodb: true - mysql: true + php: '8.3' + symfony: '7.0.*@dev' allow-unstable: true + mysql: true + mongodb: true + mongodbnew: true + + # Previous PHP versions - - name: 'Test Symfony 6.4 [Linux, PHP 8.1]' + - name: 'Test Symfony 7.1 [Linux, PHP 8.2]' os: 'ubuntu-latest' - php: '8.1' - symfony: '6.4.*@dev' + php: '8.2' + symfony: '7.0.*@dev' allow-unstable: true mysql: true mongodb: true + mongodbnew: true - - name: 'Test Symfony 7.0 [Linux, PHP 8.2]' + # Most recent versions + + - name: 'Test Symfony 7.1 [Linux, PHP 8.3]' os: 'ubuntu-latest' - php: '8.2' + php: '8.3' symfony: '7.0.*@dev' allow-unstable: true mysql: true mongodb: true mongodbnew: true + - name: 'Test Symfony 7.1 [Windows, PHP 8.3]' + os: 'windows-latest' + php: '8.3' + symfony: '7.1.*@dev' + mongodb: true + mongodbnew: true + mysql: true + allow-unstable: true + # Bleeding edge (unreleased dev versions where failures are allowed) - - name: 'Test next Symfony 7.1 [Linux, PHP 8.2] (allowed failure)' + - name: 'Test next Symfony 7.2 [Linux, PHP 8.3] (allowed failure)' os: 'ubuntu-latest' - php: '8.2' + php: '8.3' symfony: '7.1.*@dev' composer-flags: '--ignore-platform-req php' allow-unstable: true @@ -131,10 +141,10 @@ jobs: mongodb: true mongodbnew: true - - name: 'Test next Symfony 7.1 [Linux, PHP 8.3] (allowed failure)' + - name: 'Test next Symfony 7.2 [Linux, PHP 8.4] (allowed failure)' os: 'ubuntu-latest' - php: '8.3' - symfony: '7.1.*@dev' + php: '8.4' + symfony: '7.2.*@dev' composer-flags: '--ignore-platform-req php' allow-unstable: true allow-failure: true @@ -202,7 +212,7 @@ jobs: if: ${{ matrix.mongodb && !matrix.mongodbnew }} - name: 'Require Doctrine MongoDB dependencies for new symfony' - run: composer require --no-update ${{ matrix.composer-flags }} --dev --no-interaction --ansi "doctrine/mongodb-odm:^2.6" "doctrine/mongodb-odm-bundle:5.0.x-dev" + run: composer require --no-update ${{ matrix.composer-flags }} --dev --no-interaction --ansi "doctrine/mongodb-odm:^2.6" "doctrine/mongodb-odm-bundle:^5.0.0" if: ${{ matrix.mongodb && matrix.mongodbnew }} - name: 'Install dependencies' diff --git a/.make/try.mk b/.make/try.mk new file mode 100644 index 00000000..32090fb9 --- /dev/null +++ b/.make/try.mk @@ -0,0 +1,23 @@ +####### +# Try # +####### + +# Execute first command (try), unconditionnaly run second command (finally), and +# exit with first command return code. +# +# @param $1 First command +# @param $2 Second command +# +# Examples: +# +# Example #1: Run tests and remove artefacts +# +# $(call try_finally, phpunit, rm -Rf artefacts) + +define try_finally +( \ + $(strip $(1)) \ +) ; RC=$${?} \ +; $(strip $(2)) \ +&& exit $${RC} +endef diff --git a/.php-version b/.php-version index b8eb0263..2983cad0 100644 --- a/.php-version +++ b/.php-version @@ -1 +1 @@ -8.1 +8.2 diff --git a/Makefile b/Makefile index ca19c578..f38b9b9c 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ include .make/help.mk include .make/text.mk +include .make/try.mk PHP_CS_FIXER_VERSION=v3.13.0 @@ -14,7 +15,7 @@ setup: ## Install - Install deps install: setup install: - rm -f symfony composer.lock + rm -f composer.lock symfony composer config minimum-stability --unset symfony composer update --prefer-dist --ignore-platform-req=ext-mongodb @@ -32,13 +33,6 @@ install.54: symfony composer config minimum-stability dev symfony composer update --ignore-platform-req=ext-mongodb -## Install - Install Symfony 6.3 deps -install.63: setup -install.63: export SYMFONY_REQUIRE = 6.3.*@dev -install.63: - symfony composer config minimum-stability dev - symfony composer update --ignore-platform-req=ext-mongodb - ## Install - Install Symfony 6.4 deps install.64: setup install.64: export SYMFONY_REQUIRE = 6.4.*@dev @@ -61,8 +55,16 @@ install.71: symfony composer update --ignore-platform-req=ext-mongodb ## Install - Add Doctrine ODM deps -deps.odm.add: - symfony composer require --no-update --no-interaction --dev "doctrine/mongodb-odm:^2.3" "doctrine/mongodb-odm-bundle:^4.4.1" +deps.odm.add: deps.odm.add+sf64 + +## Install - Add Doctrine ODM deps for Symfony 6.4+ +deps.odm.add+sf64: + symfony composer require --no-update --no-interaction --dev "doctrine/mongodb-odm:^2.6" "doctrine/mongodb-odm-bundle:^5.0" + @$(call log_warning, Run again appropriate install target to update dependencies. Be careful not to commit compose.json changes.) + +## Install - Add Doctrine ODM deps for Symfony 5.4+ +deps.odm.add+sf54: + symfony composer require --no-update --no-interaction --dev "doctrine/mongodb-odm:^2.4" "doctrine/mongodb-odm-bundle:^4.5.1" @$(call log_warning, Run again appropriate install target to update dependencies. Be careful not to commit compose.json changes.) ## Install - Remove back Doctrine ODM deps @@ -108,10 +110,12 @@ lint.update: make php-cs-fixer.phar lint.php-cs-fixer.fix: php-cs-fixer.phar +lint.php-cs-fixer.fix: export PHP_CS_FIXER_IGNORE_ENV = 1 lint.php-cs-fixer.fix: symfony php ./php-cs-fixer.phar fix --no-interaction lint.php-cs-fixer: php-cs-fixer.phar +lint.php-cs-fixer: export PHP_CS_FIXER_IGNORE_ENV = 1 lint.php-cs-fixer: symfony php ./php-cs-fixer.phar fix --no-interaction --dry-run --diff -vvv @@ -121,5 +125,4 @@ php-cs-fixer.phar: lint.phpstan: @make deps.odm.add install >> /dev/null 2>&1 - ./vendor/bin/phpstan - @make deps.odm.rm install >> /dev/null 2>&1 + $(call try_finally, ./vendor/bin/phpstan, make deps.odm.rm install >> /dev/null 2>&1) diff --git a/composer.json b/composer.json index 4eec0c03..07d926db 100644 --- a/composer.json +++ b/composer.json @@ -41,22 +41,22 @@ "require-dev": { "ext-pdo_sqlite": "*", "composer/semver": "^3.2", - "doctrine/dbal": "^3.2|^4.0", - "doctrine/doctrine-bundle": "^2.5", - "doctrine/orm": "^2.10|^3.0", + "doctrine/dbal": "^3.8|^4.0", + "doctrine/doctrine-bundle": "^2.11", + "doctrine/orm": "^2.20|^3.0", "phpstan/phpstan": "^1.10", "phpstan/phpstan-symfony": "^1.2", - "symfony/browser-kit": "^5.4|^6.3|^7.0", - "symfony/config": "^5.4|^6.3|^7.0", - "symfony/dependency-injection": "^5.4.2|^6.3|^7.0", - "symfony/filesystem": "^5.4|^6.3|^7.0", - "symfony/form": "^5.4|^6.3|^7.0", - "symfony/framework-bundle": "^5.4|^6.3|^7.0", - "symfony/http-kernel": "^5.4.2|^6.3|^7.0", - "symfony/phpunit-bridge": "^5.4|^6.3|^7.0", - "symfony/translation": "^5.4|^6.3|^7.0", - "symfony/var-dumper": "^5.4|^6.3|^7.0", - "symfony/yaml": "^5.4|^6.3|^7.0" + "symfony/browser-kit": "^5.4.40|^6.4|^7.0", + "symfony/config": "^5.4.40|^6.4|^7.0", + "symfony/dependency-injection": "^5.4.40|^6.4|^7.0", + "symfony/filesystem": "^5.4.40|^6.4|^7.0", + "symfony/form": "^5.4.40|^6.4|^7.0", + "symfony/framework-bundle": "^5.4.40|^6.4|^7.0", + "symfony/http-kernel": "^5.4.40|^6.4|^7.0", + "symfony/phpunit-bridge": "^5.4.40|^6.4|^7.0", + "symfony/translation": "^5.4.40|^6.4|^7.0", + "symfony/var-dumper": "^5.4.40|^6.4|^7.0", + "symfony/yaml": "^5.4.40|^6.4|^7.0" }, "extra": { "branch-alias": { @@ -68,6 +68,5 @@ "*": "dist" }, "sort-packages": true - }, - "minimum-stability": "dev" + } } diff --git a/docker-compose.yml b/docker-compose.yml index 4e518920..0eedd530 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3.3' - services: db: image: mysql:8.0 diff --git a/phpstan.neon.dist b/phpstan.dist.neon similarity index 72% rename from phpstan.neon.dist rename to phpstan.dist.neon index 1fe2db1f..7b8b1967 100644 --- a/phpstan.neon.dist +++ b/phpstan.dist.neon @@ -7,5 +7,7 @@ parameters: paths: - src - checkMissingIterableValueType: false treatPhpDocTypesAsCertain: false + + ignoreErrors: + - identifier: missingType.iterableValue diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 05c51501..82b4d027 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,7 +16,7 @@ - + diff --git a/src/Bridge/Doctrine/DBAL/Types/AbstractEnumSQLDeclarationType.php b/src/Bridge/Doctrine/DBAL/Types/AbstractEnumSQLDeclarationType.php index 89b80a2d..34f94a47 100644 --- a/src/Bridge/Doctrine/DBAL/Types/AbstractEnumSQLDeclarationType.php +++ b/src/Bridge/Doctrine/DBAL/Types/AbstractEnumSQLDeclarationType.php @@ -25,7 +25,7 @@ abstract class AbstractEnumSQLDeclarationType extends AbstractEnumType /** * {@inheritdoc} */ - public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string { if (class_exists(AbstractMySQLPlatform::class)) { if (!$platform instanceof AbstractMySQLPlatform) { diff --git a/src/Bridge/Doctrine/DBAL/Types/AbstractEnumType.php b/src/Bridge/Doctrine/DBAL/Types/AbstractEnumType.php index 62c36f97..d101dc5f 100644 --- a/src/Bridge/Doctrine/DBAL/Types/AbstractEnumType.php +++ b/src/Bridge/Doctrine/DBAL/Types/AbstractEnumType.php @@ -13,9 +13,17 @@ namespace Elao\Enum\Bridge\Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\Type; +use Elao\Enum\Exception\InvalidArgumentException; if (enum_exists(ParameterType::class)) { - abstract class AbstractEnumType extends AbstractEnumTypeCommon + /** + * For doctrine/dbal 4 + * + * @internal + */ + trait DbalVersionEnumTypeTrait { /** * {@inheritdoc} @@ -26,7 +34,12 @@ public function getBindingType(): ParameterType } } } else { - abstract class AbstractEnumType extends AbstractEnumTypeCommon + /** + * For doctrine/dbal 3 + * + * @internal + */ + trait DbalVersionEnumTypeTrait { /** * {@inheritdoc} @@ -37,3 +50,132 @@ public function getBindingType(): int } } } + +abstract class AbstractEnumType extends Type +{ + use DbalVersionEnumTypeTrait; + + private bool $isIntBackedEnum; + + /** + * The enum FQCN for which we should make the DBAL conversion. + * + * @psalm-return class-string<\BackedEnum> + */ + abstract protected function getEnumClass(): string; + + /** + * What should be returned on null value from the database. + */ + protected function onNullFromDatabase(): ?\BackedEnum + { + return null; + } + + /** + * What should be returned on null value from PHP. + */ + protected function onNullFromPhp(): int|string|null + { + return null; + } + + /** + * {@inheritdoc} + * + * @param \BackedEnum|int|string|null $value + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform): string|int|null + { + if ($value !== null && !is_a($value, $this->getEnumClass())) { + $throwException = true; + if ($this->checkIfValueMatchesBackedEnumType($value)) { + $value = $this->getEnumClass()::tryFrom($this->cast($value)); + if ($value !== null) { + $throwException = false; + } + } + + if ($throwException) { + throw new InvalidArgumentException(sprintf( + 'Expected an instance of a %s. %s given.', + $this->getEnumClass(), + get_debug_type($value), + )); + } + } + + if (null === $value) { + return $this->onNullFromPhp(); + } + + return $value->value; + } + + /** + * {@inheritdoc} + * + * @param int|string|null $value The value to convert. + */ + public function convertToPHPValue($value, AbstractPlatform $platform): ?\BackedEnum + { + if (null === $value) { + return $this->onNullFromDatabase(); + } + + return $this->getEnumClass()::from($this->cast($value)); + } + + /** + * {@inheritdoc} + */ + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string + { + if ($this->isIntBackedEnum()) { + return $platform->getIntegerTypeDeclarationSQL($column); + } + + if (empty($column['length'])) { + $column['length'] = 255; + } + + return $platform->getStringTypeDeclarationSQL($column); + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform): bool + { + return true; + } + + /** + * Cast the value from database to proper enumeration internal type. + */ + private function cast(int|string $value): int|string + { + return $this->isIntBackedEnum() ? (int) $value : (string) $value; + } + + private function checkIfValueMatchesBackedEnumType(mixed $value): bool + { + return ($this->isIntBackedEnum() && \is_int($value)) || (!$this->isIntBackedEnum() && \is_string($value)); + } + + private function isIntBackedEnum(): bool + { + if (!isset($this->isIntBackedEnum)) { + $r = new \ReflectionEnum($this->getEnumClass()); + + $this->isIntBackedEnum = 'int' === (string) $r->getBackingType(); + } + + return $this->isIntBackedEnum; + } + + public function getName(): string + { + return $this->getEnumClass(); + } +} diff --git a/src/Bridge/Doctrine/DBAL/Types/AbstractEnumTypeCommon.php b/src/Bridge/Doctrine/DBAL/Types/AbstractEnumTypeCommon.php deleted file mode 100644 index 9e478910..00000000 --- a/src/Bridge/Doctrine/DBAL/Types/AbstractEnumTypeCommon.php +++ /dev/null @@ -1,146 +0,0 @@ - - */ - -namespace Elao\Enum\Bridge\Doctrine\DBAL\Types; - -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\Type; -use Elao\Enum\Exception\InvalidArgumentException; - -abstract class AbstractEnumTypeCommon extends Type -{ - private bool $isIntBackedEnum; - - /** - * The enum FQCN for which we should make the DBAL conversion. - * - * @psalm-return class-string<\BackedEnum> - */ - abstract protected function getEnumClass(): string; - - /** - * What should be returned on null value from the database. - */ - protected function onNullFromDatabase(): ?\BackedEnum - { - return null; - } - - /** - * What should be returned on null value from PHP. - */ - protected function onNullFromPhp(): int|string|null - { - return null; - } - - /** - * {@inheritdoc} - * - * @param \BackedEnum|int|string|null $value - */ - public function convertToDatabaseValue($value, AbstractPlatform $platform): string|int|null - { - if ($value !== null && !is_a($value, $this->getEnumClass())) { - $throwException = true; - if ($this->checkIfValueMatchesBackedEnumType($value)) { - $value = $this->getEnumClass()::tryFrom($this->cast($value)); - if ($value !== null) { - $throwException = false; - } - } - - if ($throwException) { - throw new InvalidArgumentException(sprintf( - 'Expected an instance of a %s. %s given.', - $this->getEnumClass(), - get_debug_type($value), - )); - } - } - - if (null === $value) { - return $this->onNullFromPhp(); - } - - return $value->value; - } - - /** - * {@inheritdoc} - * - * @param int|string|null $value The value to convert. - */ - public function convertToPHPValue($value, AbstractPlatform $platform): ?\BackedEnum - { - if (null === $value) { - return $this->onNullFromDatabase(); - } - - return $this->getEnumClass()::from($this->cast($value)); - } - - /** - * {@inheritdoc} - */ - public function getSQLDeclaration(array $column, AbstractPlatform $platform): string - { - if ($this->isIntBackedEnum()) { - return $platform->getIntegerTypeDeclarationSQL($column); - } - - if (empty($column['length'])) { - $column['length'] = 255; - } - - return method_exists($platform, 'getStringTypeDeclarationSQL') ? - $platform->getStringTypeDeclarationSQL($column) : - $platform->getVarcharTypeDeclarationSQL($column); - } - - /** - * {@inheritdoc} - */ - public function requiresSQLCommentHint(AbstractPlatform $platform): bool - { - return true; - } - - /** - * Cast the value from database to proper enumeration internal type. - */ - private function cast(int|string $value): int|string - { - return $this->isIntBackedEnum() ? (int) $value : (string) $value; - } - - private function checkIfValueMatchesBackedEnumType(mixed $value): bool - { - return ($this->isIntBackedEnum() && \is_int($value)) || (!$this->isIntBackedEnum() && \is_string($value)); - } - - protected function isIntBackedEnum(): bool - { - if (!isset($this->isIntBackedEnum)) { - $r = new \ReflectionEnum($this->getEnumClass()); - - $this->isIntBackedEnum = 'int' === (string) $r->getBackingType(); - } - - return $this->isIntBackedEnum; - } - - public function getName(): string - { - return $this->getEnumClass(); - } -} diff --git a/src/Bridge/Doctrine/DBAL/Types/AbstractFlagBagType.php b/src/Bridge/Doctrine/DBAL/Types/AbstractFlagBagType.php index 6724963a..6b74f15c 100644 --- a/src/Bridge/Doctrine/DBAL/Types/AbstractFlagBagType.php +++ b/src/Bridge/Doctrine/DBAL/Types/AbstractFlagBagType.php @@ -13,21 +13,129 @@ namespace Elao\Enum\Bridge\Doctrine\DBAL\Types; use Doctrine\DBAL\ParameterType; +use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Types\Type; +use Elao\Enum\Exception\InvalidArgumentException; +use Elao\Enum\FlagBag; if (enum_exists(ParameterType::class)) { - abstract class AbstractFlagBagType extends AbstractFlagBagTypeCommon + /** + * For doctrine/dbal 4 + * + * @internal + */ + trait DbalVersionFlagBagTypeTrait { + /** + * {@inheritdoc} + */ public function getBindingType(): ParameterType { return ParameterType::INTEGER; } } } else { - abstract class AbstractFlagBagType extends AbstractFlagBagTypeCommon + /** + * For doctrine/dbal 3 + * + * @internal + */ + trait DbalVersionFlagBagTypeTrait { + /** + * {@inheritdoc} + */ public function getBindingType(): int { return ParameterType::INTEGER; } } } + +abstract class AbstractFlagBagType extends Type +{ + use DbalVersionFlagBagTypeTrait; + + /** + * The enum FQCN for which we should make the DBAL conversion. + * + * @psalm-return class-string<\BackedEnum> + */ + abstract protected function getEnumClass(): string; + + /** + * What should be returned on null value from the database. + * + * @return FlagBag<\BackedEnum>|null + */ + protected function onNullFromDatabase(): ?FlagBag + { + return null; + } + + /** + * What should be returned on null value from PHP. + */ + protected function onNullFromPhp(): int|null + { + return null; + } + + /** + * {@inheritdoc} + * + * @param FlagBag<\BackedEnum>|null $value + */ + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?int + { + if ($value !== null && !$value instanceof FlagBag) { + throw new InvalidArgumentException(sprintf( + 'Expected an instance of a %s. %s given.', + FlagBag::class, + get_debug_type($value), + )); + } + + if (null === $value) { + return $this->onNullFromPhp(); + } + + return $value->getValue(); + } + + /** + * {@inheritdoc} + * + * @param int|null $value The value to convert. + * + * @return FlagBag<\BackedEnum>|null + */ + public function convertToPHPValue($value, AbstractPlatform $platform): ?FlagBag + { + $value = parent::convertToPHPValue($value, $platform); + + if (null === $value) { + return $this->onNullFromDatabase(); + } + + return new FlagBag($this->getEnumClass(), $value); + } + + /** + * {@inheritdoc} + */ + public function requiresSQLCommentHint(AbstractPlatform $platform): bool + { + return true; + } + + public function getName(): string + { + return $this->getEnumClass(); + } + + public function getSQLDeclaration(array $column, AbstractPlatform $platform): string + { + return $platform->getIntegerTypeDeclarationSQL($column); + } +} diff --git a/src/Bridge/Doctrine/DBAL/Types/AbstractFlagBagTypeCommon.php b/src/Bridge/Doctrine/DBAL/Types/AbstractFlagBagTypeCommon.php deleted file mode 100644 index 6fb21e66..00000000 --- a/src/Bridge/Doctrine/DBAL/Types/AbstractFlagBagTypeCommon.php +++ /dev/null @@ -1,104 +0,0 @@ - - */ - -namespace Elao\Enum\Bridge\Doctrine\DBAL\Types; - -use Doctrine\DBAL\Platforms\AbstractPlatform; -use Doctrine\DBAL\Types\Type; -use Elao\Enum\Exception\InvalidArgumentException; -use Elao\Enum\FlagBag; - -abstract class AbstractFlagBagTypeCommon extends Type -{ - /** - * The enum FQCN for which we should make the DBAL conversion. - * - * @psalm-return class-string<\BackedEnum> - */ - abstract protected function getEnumClass(): string; - - /** - * What should be returned on null value from the database. - * - * @return FlagBag<\BackedEnum>|null - */ - protected function onNullFromDatabase(): ?FlagBag - { - return null; - } - - /** - * What should be returned on null value from PHP. - */ - protected function onNullFromPhp(): int|null - { - return null; - } - - /** - * {@inheritdoc} - * - * @param FlagBag<\BackedEnum>|null $value - */ - public function convertToDatabaseValue($value, AbstractPlatform $platform): ?int - { - if ($value !== null && !$value instanceof FlagBag) { - throw new InvalidArgumentException(sprintf( - 'Expected an instance of a %s. %s given.', - FlagBag::class, - get_debug_type($value), - )); - } - - if (null === $value) { - return $this->onNullFromPhp(); - } - - return $value->getValue(); - } - - /** - * {@inheritdoc} - * - * @param int|null $value The value to convert. - * - * @return FlagBag<\BackedEnum>|null - */ - public function convertToPHPValue($value, AbstractPlatform $platform): ?FlagBag - { - $value = parent::convertToPHPValue($value, $platform); - - if (null === $value) { - return $this->onNullFromDatabase(); - } - - return new FlagBag($this->getEnumClass(), $value); - } - - /** - * {@inheritdoc} - */ - public function requiresSQLCommentHint(AbstractPlatform $platform): bool - { - return true; - } - - public function getName(): string - { - return $this->getEnumClass(); - } - - public function getSQLDeclaration(array $column, AbstractPlatform $platform): string - { - return $platform->getIntegerTypeDeclarationSQL($column); - } -} diff --git a/src/Bridge/Symfony/Bundle/config/services.php b/src/Bridge/Symfony/Bundle/config/services.php index f817fb59..bbd283d4 100644 --- a/src/Bridge/Symfony/Bundle/config/services.php +++ b/src/Bridge/Symfony/Bundle/config/services.php @@ -15,9 +15,11 @@ use Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver; use Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\QueryBodyBackedEnumValueResolver; use Symfony\Component\HttpKernel\Controller\ArgumentResolver\BackedEnumValueResolver as SymfonyBackedEnumValueResolver; +use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface; return static function (ContainerConfigurator $container) { - if (!class_exists(SymfonyBackedEnumValueResolver::class)) { + // To be dropped when Symfony 5.4 support is dropped + if (!class_exists(SymfonyBackedEnumValueResolver::class) && interface_exists(ArgumentValueResolverInterface::class)) { $container->services() ->set(BackedEnumValueResolver::class)->tag('controller.argument_value_resolver', [ 'priority' => 105, // Prior RequestAttributeValueResolver diff --git a/src/Bridge/Symfony/HttpKernel/Controller/ArgumentResolver/BackedEnumValueResolver.php b/src/Bridge/Symfony/HttpKernel/Controller/ArgumentResolver/BackedEnumValueResolver.php index 7b2feb13..7c8cf9a8 100644 --- a/src/Bridge/Symfony/HttpKernel/Controller/ArgumentResolver/BackedEnumValueResolver.php +++ b/src/Bridge/Symfony/HttpKernel/Controller/ArgumentResolver/BackedEnumValueResolver.php @@ -18,7 +18,13 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +if (!interface_exists(ArgumentValueResolverInterface::class)) { + // Interface does not exist anymore as of Symfony 7+ + return; +} + // Legacy (<6.1) resolver +// To be dropped when Symfony 5.4 is EOL. /** * Attempt to resolve backed enum cases from request attributes, for a route path parameter, * leading to a 404 Not Found if the attribute value isn't a valid backing value for the enum type. diff --git a/src/Bridge/Symfony/HttpKernel/Controller/ArgumentResolver/QueryBodyBackedEnumValueResolver.php b/src/Bridge/Symfony/HttpKernel/Controller/ArgumentResolver/QueryBodyBackedEnumValueResolver.php index 2a0445c7..057683d8 100644 --- a/src/Bridge/Symfony/HttpKernel/Controller/ArgumentResolver/QueryBodyBackedEnumValueResolver.php +++ b/src/Bridge/Symfony/HttpKernel/Controller/ArgumentResolver/QueryBodyBackedEnumValueResolver.php @@ -62,7 +62,8 @@ function resolveValues(Request $request, ArgumentMetadata $argument): array } // Legacy (<6.2) resolver -if (!interface_exists(ValueResolverInterface::class)) { +// To be dropped when Symfony 5.4 is EOL. +if (!interface_exists(ValueResolverInterface::class) && interface_exists(ArgumentValueResolverInterface::class)) { /** * @final */ diff --git a/tests/Fixtures/Integration/Symfony/config/config-64+.yaml b/tests/Fixtures/Integration/Symfony/config/config-64+.yaml new file mode 100644 index 00000000..71d6bc47 --- /dev/null +++ b/tests/Fixtures/Integration/Symfony/config/config-64+.yaml @@ -0,0 +1,7 @@ +framework: + session: + cookie_secure: auto + cookie_samesite: lax + handle_all_throwables: true + php_errors: + log: true diff --git a/tests/Fixtures/Integration/Symfony/config/config.yaml b/tests/Fixtures/Integration/Symfony/config/config.yaml index b40a5732..89dc35da 100644 --- a/tests/Fixtures/Integration/Symfony/config/config.yaml +++ b/tests/Fixtures/Integration/Symfony/config/config.yaml @@ -1,56 +1,55 @@ framework: - secret: 'elao' - form: true - router: - strict_requirements: '%kernel.debug%' - utf8: true - session: - handler_id: null - storage_factory_id: 'session.storage.factory.mock_file' - test: ~ - assets: false - http_method_override: false + secret: 'elao' + form: true + router: + strict_requirements: '%kernel.debug%' + utf8: true + session: + handler_id: null + storage_factory_id: 'session.storage.factory.mock_file' + test: ~ + assets: false + http_method_override: false doctrine: - dbal: - url: '%env(resolve:DOCTRINE_DBAL_URL)%' + dbal: + url: '%env(resolve:DOCTRINE_DBAL_URL)%' - orm: - auto_generate_proxy_classes: '%kernel.debug%' - mappings: - App: - is_bundle: false - type: attribute - dir: '%kernel.project_dir%/src/Entity' - prefix: 'App\Entity' - alias: App + orm: + auto_generate_proxy_classes: '%kernel.debug%' + report_fields_where_declared: true + enable_lazy_ghost_objects: true + controller_resolver: + auto_mapping: false + + mappings: + App: + is_bundle: false + type: attribute + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: App elao_enum: - doctrine: - types: - App\Enum\Suit: - default: !php/const App\Enum\Suit::Spades - permissions_flagbag: - class: App\Enum\Permissions - default: !php/const App\Enum\Permissions::Read - type: flagbag + doctrine: + types: + App\Enum\Suit: + default: !php/const App\Enum\Suit::Spades + permissions_flagbag: + class: App\Enum\Permissions + default: !php/const App\Enum\Permissions::Read + type: flagbag services: - # Registers these controllers as a service so that we have the - # \Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver enabled on it: - App\Controller\BackedEnumValueResolverController: - autoconfigure: true - autowire: true - App\Controller\QueryBodyBackedEnumValueResolverController: - autoconfigure: true - autowire: true - App\ControllerAnnotation\BackedEnumValueResolverController: - autoconfigure: true - autowire: true - App\ControllerAnnotation\QueryBodyBackedEnumValueResolverController: - autoconfigure: true - autowire: true + # Registers these controllers as a service so that we have the + # \Symfony\Component\HttpKernel\Controller\ArgumentResolver\ServiceValueResolver enabled on it: + App\Controller\BackedEnumValueResolverController: + autoconfigure: true + autowire: true + App\Controller\QueryBodyBackedEnumValueResolverController: + autoconfigure: true + autowire: true - logger: - class: Psr\Log\NullLogger - public: false + logger: + class: Psr\Log\NullLogger + public: false diff --git a/tests/Fixtures/Integration/Symfony/config/doctrine-new.yaml b/tests/Fixtures/Integration/Symfony/config/doctrine-new.yaml deleted file mode 100644 index e2affd90..00000000 --- a/tests/Fixtures/Integration/Symfony/config/doctrine-new.yaml +++ /dev/null @@ -1,6 +0,0 @@ -doctrine: - orm: - report_fields_where_declared: true - enable_lazy_ghost_objects: true - controller_resolver: - auto_mapping: true diff --git a/tests/Fixtures/Integration/Symfony/config/doctrine-old.yaml b/tests/Fixtures/Integration/Symfony/config/doctrine-old.yaml deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/Fixtures/Integration/Symfony/config/routing-annotation.yaml b/tests/Fixtures/Integration/Symfony/config/routing-annotation.yaml index 14f4ce66..a9bbdc3a 100644 --- a/tests/Fixtures/Integration/Symfony/config/routing-annotation.yaml +++ b/tests/Fixtures/Integration/Symfony/config/routing-annotation.yaml @@ -1,3 +1,3 @@ controllers: - resource: ../src/ControllerAnnotation/ + resource: ../src/Controller/ type: annotation diff --git a/tests/Fixtures/Integration/Symfony/src/Controller/BackedEnumValueResolverController.php b/tests/Fixtures/Integration/Symfony/src/Controller/BackedEnumValueResolverController.php index 8168a50c..1f711d95 100644 --- a/tests/Fixtures/Integration/Symfony/src/Controller/BackedEnumValueResolverController.php +++ b/tests/Fixtures/Integration/Symfony/src/Controller/BackedEnumValueResolverController.php @@ -19,6 +19,10 @@ use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; +if (!class_exists(Route::class)) { + class_alias(\Symfony\Component\Routing\Annotation\Route::class, Route::class); +} + #[Route(path: '/resolver', name: 'from-attributes')] class BackedEnumValueResolverController extends AbstractController { diff --git a/tests/Fixtures/Integration/Symfony/src/Controller/QueryBodyBackedEnumValueResolverController.php b/tests/Fixtures/Integration/Symfony/src/Controller/QueryBodyBackedEnumValueResolverController.php index 465af7ca..575847a3 100644 --- a/tests/Fixtures/Integration/Symfony/src/Controller/QueryBodyBackedEnumValueResolverController.php +++ b/tests/Fixtures/Integration/Symfony/src/Controller/QueryBodyBackedEnumValueResolverController.php @@ -21,6 +21,10 @@ use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\Test\VarDumperTestTrait; +if (!class_exists(Route::class)) { + class_alias(\Symfony\Component\Routing\Annotation\Route::class, Route::class); +} + #[Route(path: '/resolver', name: 'from-query-body')] class QueryBodyBackedEnumValueResolverController extends AbstractController { diff --git a/tests/Fixtures/Integration/Symfony/src/ControllerAnnotation/BackedEnumValueResolverController.php b/tests/Fixtures/Integration/Symfony/src/ControllerAnnotation/BackedEnumValueResolverController.php deleted file mode 100644 index 07f1852c..00000000 --- a/tests/Fixtures/Integration/Symfony/src/ControllerAnnotation/BackedEnumValueResolverController.php +++ /dev/null @@ -1,49 +0,0 @@ - - */ - -namespace App\ControllerAnnotation; - -use App\Enum\Suit; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; -use Symfony\Component\VarDumper\Dumper\CliDumper; -use Symfony\Component\VarDumper\Test\VarDumperTestTrait; - -#[Route(path: '/resolver', name: 'from-attributes')] -class BackedEnumValueResolverController extends AbstractController -{ - use VarDumperTestTrait; - - public function __construct() - { - $this->setUpVarDumper([], CliDumper::DUMP_LIGHT_ARRAY); - } - - #[Route(path: '/from-attributes/{suit}')] - public function fromAttributes(Suit $suit): Response - { - return new Response($this->getDump($suit)); - } - - #[Route(path: '/from-attributes-nullable/{suit}')] - public function fromAttributesNullable(?Suit $suit = null): Response - { - return new Response($this->getDump($suit)); - } - - #[Route(path: '/from-attributes-with-default')] - public function fromAttributesWithDefault(Suit $suit = Suit::Spades): Response - { - return new Response($this->getDump($suit)); - } -} diff --git a/tests/Fixtures/Integration/Symfony/src/ControllerAnnotation/QueryBodyBackedEnumValueResolverController.php b/tests/Fixtures/Integration/Symfony/src/ControllerAnnotation/QueryBodyBackedEnumValueResolverController.php deleted file mode 100644 index b3c6702e..00000000 --- a/tests/Fixtures/Integration/Symfony/src/ControllerAnnotation/QueryBodyBackedEnumValueResolverController.php +++ /dev/null @@ -1,89 +0,0 @@ - - */ - -namespace App\ControllerAnnotation; - -use App\Enum\Suit; -use Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\Attributes\BackedEnumFromBody; -use Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\Attributes\BackedEnumFromQuery; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\Routing\Annotation\Route; -use Symfony\Component\VarDumper\Dumper\CliDumper; -use Symfony\Component\VarDumper\Test\VarDumperTestTrait; - -#[Route(path: '/resolver', name: 'from-query-body')] -class QueryBodyBackedEnumValueResolverController extends AbstractController -{ - use VarDumperTestTrait; - - public function __construct() - { - $this->setUpVarDumper([], CliDumper::DUMP_LIGHT_ARRAY); - } - - #[Route(path: '/from-query')] - public function fromQuery( - #[BackedEnumFromQuery] - Suit $suit - ): Response { - return new Response($this->getDump($suit)); - } - - #[Route(path: '/from-query-nullable')] - public function fromQueryNullable( - #[BackedEnumFromQuery] - ?Suit $suit - ): Response { - return new Response($this->getDump($suit)); - } - - #[Route(path: '/from-query-with-default')] - public function fromQueryWithDefault( - #[BackedEnumFromQuery] - ?Suit $suit = Suit::Hearts - ): Response { - return new Response($this->getDump($suit)); - } - - #[Route(path: '/from-query-with-default-non-nullable')] - public function fromQueryWithDefaultNonNullable( - #[BackedEnumFromQuery] - Suit $suit = Suit::Hearts - ): Response { - return new Response($this->getDump($suit)); - } - - #[Route(path: '/from-query-variadics')] - public function fromQueryVariadics( - #[BackedEnumFromQuery] - Suit ...$suit - ): Response { - return new Response($this->getDump($suit)); - } - - #[Route(path: '/from-body', methods: 'POST')] - public function fromBody( - #[BackedEnumFromBody] - Suit $suit - ): Response { - return new Response($this->getDump($suit)); - } - - #[Route(path: '/from-body-variadics', methods: 'POST')] - public function fromBodyVariadics( - #[BackedEnumFromBody] - Suit ...$suit - ): Response { - return new Response($this->getDump($suit)); - } -} diff --git a/tests/Fixtures/Integration/Symfony/src/Kernel.php b/tests/Fixtures/Integration/Symfony/src/Kernel.php index 5f89b7a1..5f671024 100644 --- a/tests/Fixtures/Integration/Symfony/src/Kernel.php +++ b/tests/Fixtures/Integration/Symfony/src/Kernel.php @@ -40,16 +40,10 @@ public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load($this->getProjectDir() . '/config/config.yaml'); - // TODO: we can remove when 5.4 is dropped - if (InstalledVersions::satisfies(new VersionParser(), 'doctrine/doctrine-bundle', '>=2.10')) { - $loader->load($this->getProjectDir() . '/config/doctrine-new.yaml'); - } else { - $loader->load($this->getProjectDir() . '/config/doctrine-old.yaml'); - } - - // TODO: we can remove when 5.4 and 6.3 is dropped + // TODO: we can remove when Sf 5.4 is dropped if (InstalledVersions::satisfies(new VersionParser(), 'symfony/http-kernel', '>=6.4')) { $loader->load($this->getProjectDir() . '/config/config-routing-attribute.yaml'); + $loader->load($this->getProjectDir() . '/config/config-64+.yaml'); } else { $loader->load($this->getProjectDir() . '/config/config-routing-annotation.yaml'); }