diff --git a/composer.json b/composer.json index 846f7ef..1d3f359 100644 --- a/composer.json +++ b/composer.json @@ -35,13 +35,11 @@ }, "require": { "moneyphp/money": "^3.0|^4.0", - "doctrine/dbal": "^2.8|^3.0", + "doctrine/dbal": "^3.6|^4.0", "php": "^8.4", - "ramsey/uuid-doctrine": "^1.4" + "ramsey/uuid-doctrine": "^1.4|^2.0" }, "require-dev": { - "doctrine/cache": "~1.0", - "doctrine/annotations": "~1.0", "symfony/framework-bundle": "^7.0", "symfony/yaml": "^7.0", "assoconnect/php-quality-config": "^2.2" diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 81702d8..51d6bae 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,7 +1,18 @@ -parameters: - paths: - - src/ - - tests/ - -includes: - - vendor/assoconnect/php-quality-config/phpstan.extension.neon \ No newline at end of file +parameters: + paths: + - src/ + - tests/ + + # The errors below depend on which DBAL major is installed (^3.6|^4.0 are both supported), + # so each analysis run only matches half of them. + reportUnmatchedIgnoredErrors: false + ignoreErrors: + # DBAL 3 only: static factories removed in DBAL 4 + - '#Call to an undefined static method Doctrine\\DBAL\\Types\\ConversionException::conversionFailed(Format|InvalidType)\(\)\.#' + # DBAL 4 only: exception classes absent from DBAL 3 + - '#unknown class Doctrine\\DBAL\\Types\\Exception\\Invalid(Format|Type)#' + # Removed from the base Type class in DBAL 4 but still defined by our types for DBAL 3 + - '#Call to an undefined method Doctrine\\DBAL\\Types\\Type::requiresSQLCommentHint\(\)\.#' + +includes: + - vendor/assoconnect/php-quality-config/phpstan.extension.neon diff --git a/src/Doctrine/DBAL/Types/AbstractFixedLengthStringType.php b/src/Doctrine/DBAL/Types/AbstractFixedLengthStringType.php index a0cfd38..9ce731b 100644 --- a/src/Doctrine/DBAL/Types/AbstractFixedLengthStringType.php +++ b/src/Doctrine/DBAL/Types/AbstractFixedLengthStringType.php @@ -15,7 +15,7 @@ abstract class AbstractFixedLengthStringType extends Type public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string { $fieldDeclaration['length'] = $this->getLength(); - return $platform->getVarcharTypeDeclarationSQL($fieldDeclaration); + return $platform->getStringTypeDeclarationSQL($fieldDeclaration); } abstract protected function getLength(): int; diff --git a/src/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsType.php b/src/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsType.php index 4263161..a9639a4 100644 --- a/src/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsType.php +++ b/src/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsType.php @@ -8,12 +8,16 @@ use DateTimeInterface; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; +use Doctrine\DBAL\Types\Exception\InvalidFormat; +use Doctrine\DBAL\Types\Exception\InvalidType; use Doctrine\DBAL\Types\Type; class DateTimeImmutableMicroSecondsType extends Type { public const NAME = 'datetime_immutable_micro_seconds'; + private const FORMAT = 'Y-m-d H:i:s.v'; + public function getName(): string { return self::NAME; @@ -30,18 +34,13 @@ public function convertToPHPValue($value, AbstractPlatform $platform): ?DateTime return $value; } - $date = DateTimeImmutable::createFromFormat('Y-m-d H:i:s.v', $value); + $date = DateTimeImmutable::createFromFormat(self::FORMAT, $value); if ($date === false) { try { $dateTime = new DateTimeImmutable($value); } catch (\Exception $e) { - throw ConversionException::conversionFailedFormat( - $value, - $this->getName(), - 'Y-m-d H:i:s.v', - $e // Previous exception bundled - ); + throw $this->createInvalidFormatException($value, $e); } return $dateTime; } @@ -56,13 +55,40 @@ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?str } if ($value instanceof DateTimeImmutable) { - return $value->format('Y-m-d H:i:s.v'); + return $value->format(self::FORMAT); + } + + throw $this->createInvalidTypeException($value); + } + + /** + * DBAL 4 replaced the ConversionException static factories with dedicated exception classes. + * The runtime conditionals below can be inlined once DBAL 3 support is dropped. + * Excluded from coverage: only one branch can run for a given installed DBAL major. + * + * @codeCoverageIgnore + */ + private function createInvalidFormatException(mixed $value, \Throwable $previous): ConversionException + { + if (class_exists(InvalidFormat::class)) { + return InvalidFormat::new($value, self::NAME, self::FORMAT, $previous); + } + + return ConversionException::conversionFailedFormat($value, self::NAME, self::FORMAT, $previous); + } + + /** + * @codeCoverageIgnore + */ + private function createInvalidTypeException(mixed $value): ConversionException + { + if (class_exists(InvalidType::class)) { + return InvalidType::new($value, self::NAME, ['null', DateTimeImmutable::class]); } - throw ConversionException::conversionFailedInvalidType( - $value, - $this->getName(), - ['null', DateTimeImmutable::class] - ); + return ConversionException::conversionFailedInvalidType($value, self::NAME, [ + 'null', + DateTimeImmutable::class, + ]); } } diff --git a/tests/Doctrine/DBAL/Types/BelgianEnterpriseNumberTypeTest.php b/tests/Doctrine/DBAL/Types/BelgianEnterpriseNumberTypeTest.php index b09ec90..27ed719 100644 --- a/tests/Doctrine/DBAL/Types/BelgianEnterpriseNumberTypeTest.php +++ b/tests/Doctrine/DBAL/Types/BelgianEnterpriseNumberTypeTest.php @@ -6,6 +6,7 @@ use AssoConnect\DoctrineTypesBundle\Doctrine\DBAL\Types\BelgianEnterpriseNumberType; use AssoConnect\DoctrineTypesBundle\Tests\TypeTestCase; +use Doctrine\DBAL\Platforms\AbstractPlatform; class BelgianEnterpriseNumberTypeTest extends TypeTestCase { @@ -16,16 +17,18 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(BelgianEnterpriseNumberType::NAME, $this->type->getName()); + self::assertSame(BelgianEnterpriseNumberType::NAME, (new BelgianEnterpriseNumberType())->getName()); } public function testGetSQLDeclaration(): void { - $this->abstractPlatform - ->method('getVarcharTypeDeclarationSQL') + $platform = $this->createMock(AbstractPlatform::class); + $platform + ->expects(self::once()) + ->method('getStringTypeDeclarationSQL') ->with(['length' => BelgianEnterpriseNumberType::LENGTH]) ->willReturn('VARCHAR'); - self::assertSame('VARCHAR', $this->type->getSQLDeclaration([], $this->abstractPlatform)); + self::assertSame('VARCHAR', $this->type->getSQLDeclaration([], $platform)); } } diff --git a/tests/Doctrine/DBAL/Types/BicTypeTest.php b/tests/Doctrine/DBAL/Types/BicTypeTest.php index 277d2e2..91802de 100644 --- a/tests/Doctrine/DBAL/Types/BicTypeTest.php +++ b/tests/Doctrine/DBAL/Types/BicTypeTest.php @@ -16,12 +16,12 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(BicType::NAME, $this->type->getName()); + self::assertSame(BicType::NAME, (new BicType())->getName()); } public function testGetSQLDeclaration(): void { - $this->abstractPlatform->method('getVarcharTypeDeclarationSQL')->willReturn('VARCHAR'); + $this->abstractPlatform->method('getStringTypeDeclarationSQL')->willReturn('VARCHAR'); self::assertSame('VARCHAR', $this->type->getSQLDeclaration([], $this->abstractPlatform)); } } diff --git a/tests/Doctrine/DBAL/Types/CountryTypeTest.php b/tests/Doctrine/DBAL/Types/CountryTypeTest.php index 995aafc..998d070 100644 --- a/tests/Doctrine/DBAL/Types/CountryTypeTest.php +++ b/tests/Doctrine/DBAL/Types/CountryTypeTest.php @@ -6,6 +6,7 @@ use AssoConnect\DoctrineTypesBundle\Doctrine\DBAL\Types\CountryType; use AssoConnect\DoctrineTypesBundle\Tests\TypeTestCase; +use Doctrine\DBAL\Platforms\AbstractPlatform; class CountryTypeTest extends TypeTestCase { @@ -16,16 +17,18 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(CountryType::NAME, $this->type->getName()); + self::assertSame(CountryType::NAME, (new CountryType())->getName()); } public function testGetSQLDeclaration(): void { - $this->abstractPlatform - ->method('getVarcharTypeDeclarationSQL') + $platform = $this->createMock(AbstractPlatform::class); + $platform + ->expects(self::once()) + ->method('getStringTypeDeclarationSQL') ->with(['fixed' => true, 'length' => CountryType::LENGTH]) ->willReturn('VARCHAR'); - self::assertSame('VARCHAR', $this->type->getSQLDeclaration([], $this->abstractPlatform)); + self::assertSame('VARCHAR', $this->type->getSQLDeclaration([], $platform)); } } diff --git a/tests/Doctrine/DBAL/Types/CurrencyTypeTest.php b/tests/Doctrine/DBAL/Types/CurrencyTypeTest.php index 3a236ff..ca4cad9 100644 --- a/tests/Doctrine/DBAL/Types/CurrencyTypeTest.php +++ b/tests/Doctrine/DBAL/Types/CurrencyTypeTest.php @@ -17,7 +17,7 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(CurrencyType::NAME, $this->type->getName()); + self::assertSame(CurrencyType::NAME, (new CurrencyType())->getName()); } public function testConvertToDatabaseValueValid(): void diff --git a/tests/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsTypeTest.php b/tests/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsTypeTest.php index edf7d8f..d87164d 100644 --- a/tests/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsTypeTest.php +++ b/tests/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsTypeTest.php @@ -7,28 +7,37 @@ use AssoConnect\DoctrineTypesBundle\Doctrine\DBAL\Types\DateTimeImmutableMicroSecondsType; use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\ConversionException; -use Doctrine\DBAL\Types\Type; -use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; class DateTimeImmutableMicroSecondsTypeTest extends TestCase { - /** @var AbstractPlatform|MockObject */ - protected AbstractPlatform $platform; + protected AbstractPlatform&Stub $platform; - protected Type $type; + protected DateTimeImmutableMicroSecondsType $type; protected function setUp(): void { $this->type = new DateTimeImmutableMicroSecondsType(); - $this->platform = $this->getMockForAbstractClass(AbstractPlatform::class); + $this->platform = self::createStub(AbstractPlatform::class); + } + + public function testGetName(): void + { + self::assertSame(DateTimeImmutableMicroSecondsType::NAME, $this->type->getName()); + } + + public function testGetSQLDeclaration(): void + { + self::assertSame('DATETIME(3)', $this->type->getSQLDeclaration([], $this->platform)); + self::assertSame('TIMESTAMP', $this->type->getSQLDeclaration(['version' => true], $this->platform)); } /** * @param mixed $value - * - * @dataProvider invalidPHPValuesProvider */ + #[DataProvider('invalidPHPValuesProvider')] public function testInvalidTypeConversionToDatabaseValue($value): void { $this->expectException(ConversionException::class); @@ -60,9 +69,20 @@ public function testNullConversion(): void self::assertNull($this->type->convertToPHPValue(null, $this->platform)); } - /** - * @dataProvider provideDateTimeValues - */ + public function testNullConversionToDatabaseValue(): void + { + self::assertNull($this->type->convertToDatabaseValue(null, $this->platform)); + } + + public function testConvertFormattedStringToPHPValue(): void + { + $date = $this->type->convertToPHPValue('1985-09-01 10:11:12.123', $this->platform); + + self::assertInstanceOf(\DateTimeImmutable::class, $date); + self::assertSame('1985-09-01 10:11:12.123', $date->format('Y-m-d H:i:s.v')); + } + + #[DataProvider('provideDateTimeValues')] public function testDateConvertsToDatabaseValue(string $datetime, string $expectedDatetimeResult): void { $date = new \DateTimeImmutable($datetime); @@ -70,9 +90,7 @@ public function testDateConvertsToDatabaseValue(string $datetime, string $expect self::assertSame($expectedDatetimeResult, $this->type->convertToDatabaseValue($date, $this->platform)); } - /** - * @dataProvider provideDateTimeValues - */ + #[DataProvider('provideDateTimes')] public function testConvertDateToPHPValue(string $datetime): void { $date = new \DateTimeImmutable($datetime); @@ -80,6 +98,16 @@ public function testConvertDateToPHPValue(string $datetime): void self::assertSame($date, $this->type->convertToPHPValue($date, $this->platform)); } + /** + * @return iterable + */ + public static function provideDateTimes(): iterable + { + foreach (self::provideDateTimeValues() as $key => [$datetime]) { + yield $key => [$datetime]; + } + } + /** * @return iterable @@ -103,6 +131,7 @@ public function testDateResetsNonDatePartsToZeroUnixTimeValues(): void { $date = $this->type->convertToPHPValue('1985-09-01', $this->platform); + self::assertInstanceOf(\DateTimeImmutable::class, $date); self::assertEquals('00:00:00.000', $date->format('H:i:s.v')); } @@ -111,9 +140,11 @@ public function testDateResetsSummerTimeAffection(): void date_default_timezone_set('Europe/Berlin'); $date = $this->type->convertToPHPValue('2009-08-01', $this->platform); + self::assertInstanceOf(\DateTimeImmutable::class, $date); self::assertEquals('2009-08-01 00:00:00.000', $date->format('Y-m-d H:i:s.v')); $date = $this->type->convertToPHPValue('2009-11-01', $this->platform); + self::assertInstanceOf(\DateTimeImmutable::class, $date); self::assertEquals('2009-11-01 00:00:00.000', $date->format('Y-m-d H:i:s.v')); } diff --git a/tests/Doctrine/DBAL/Types/EmailTypeTest.php b/tests/Doctrine/DBAL/Types/EmailTypeTest.php index 1e26670..38c1b8f 100644 --- a/tests/Doctrine/DBAL/Types/EmailTypeTest.php +++ b/tests/Doctrine/DBAL/Types/EmailTypeTest.php @@ -16,6 +16,6 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(EmailType::NAME, $this->type->getName()); + self::assertSame(EmailType::NAME, (new EmailType())->getName()); } } diff --git a/tests/Doctrine/DBAL/Types/FrenchSiretTypeTest.php b/tests/Doctrine/DBAL/Types/FrenchSiretTypeTest.php index e414db5..5d6f2c6 100644 --- a/tests/Doctrine/DBAL/Types/FrenchSiretTypeTest.php +++ b/tests/Doctrine/DBAL/Types/FrenchSiretTypeTest.php @@ -16,6 +16,6 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(FrenchSiretType::NAME, $this->type->getName()); + self::assertSame(FrenchSiretType::NAME, (new FrenchSiretType())->getName()); } } diff --git a/tests/Doctrine/DBAL/Types/LastDigitsUsSocialSecurityNumberTypeTest.php b/tests/Doctrine/DBAL/Types/LastDigitsUsSocialSecurityNumberTypeTest.php index d28fcc4..41d7a9c 100644 --- a/tests/Doctrine/DBAL/Types/LastDigitsUsSocialSecurityNumberTypeTest.php +++ b/tests/Doctrine/DBAL/Types/LastDigitsUsSocialSecurityNumberTypeTest.php @@ -16,6 +16,9 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(LastDigitsUsSocialSecurityNumberType::NAME, $this->type->getName()); + self::assertSame( + LastDigitsUsSocialSecurityNumberType::NAME, + (new LastDigitsUsSocialSecurityNumberType())->getName() + ); } } diff --git a/tests/Doctrine/DBAL/Types/LatitudeTypeTest.php b/tests/Doctrine/DBAL/Types/LatitudeTypeTest.php index 03c3d70..119a986 100644 --- a/tests/Doctrine/DBAL/Types/LatitudeTypeTest.php +++ b/tests/Doctrine/DBAL/Types/LatitudeTypeTest.php @@ -16,7 +16,7 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(LatitudeType::NAME, $this->type->getName()); + self::assertSame(LatitudeType::NAME, (new LatitudeType())->getName()); } public function testGetSQLDeclaration(): void diff --git a/tests/Doctrine/DBAL/Types/SpanishNifTypeTest.php b/tests/Doctrine/DBAL/Types/SpanishNifTypeTest.php index c807fcc..3c4e513 100644 --- a/tests/Doctrine/DBAL/Types/SpanishNifTypeTest.php +++ b/tests/Doctrine/DBAL/Types/SpanishNifTypeTest.php @@ -6,6 +6,7 @@ use AssoConnect\DoctrineTypesBundle\Doctrine\DBAL\Types\SpanishNifType; use AssoConnect\DoctrineTypesBundle\Tests\TypeTestCase; +use Doctrine\DBAL\Platforms\AbstractPlatform; class SpanishNifTypeTest extends TypeTestCase { @@ -16,16 +17,18 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(SpanishNifType::NAME, $this->type->getName()); + self::assertSame(SpanishNifType::NAME, (new SpanishNifType())->getName()); } public function testGetSQLDeclaration(): void { - $this->abstractPlatform - ->method('getVarcharTypeDeclarationSQL') + $platform = $this->createMock(AbstractPlatform::class); + $platform + ->expects(self::once()) + ->method('getStringTypeDeclarationSQL') ->with(['length' => SpanishNifType::LENGTH]) ->willReturn('VARCHAR'); - self::assertSame('VARCHAR', $this->type->getSQLDeclaration([], $this->abstractPlatform)); + self::assertSame('VARCHAR', $this->type->getSQLDeclaration([], $platform)); } } diff --git a/tests/Doctrine/DBAL/Types/UsSocialSecurityNumberTypeTest.php b/tests/Doctrine/DBAL/Types/UsSocialSecurityNumberTypeTest.php index fb54fd6..d72d059 100644 --- a/tests/Doctrine/DBAL/Types/UsSocialSecurityNumberTypeTest.php +++ b/tests/Doctrine/DBAL/Types/UsSocialSecurityNumberTypeTest.php @@ -16,6 +16,6 @@ protected function getClass(): string public function testGetName(): void { - self::assertSame(UsSocialSecurityNumberType::NAME, $this->type->getName()); + self::assertSame(UsSocialSecurityNumberType::NAME, (new UsSocialSecurityNumberType())->getName()); } } diff --git a/tests/TypeTestCase.php b/tests/TypeTestCase.php index d444c7b..61118f7 100644 --- a/tests/TypeTestCase.php +++ b/tests/TypeTestCase.php @@ -6,15 +6,12 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Types\Type; -use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; abstract class TypeTestCase extends TestCase { - /** - * @var MockObject|AbstractPlatform - */ - protected $abstractPlatform; + protected AbstractPlatform&Stub $abstractPlatform; protected Type $type; @@ -25,15 +22,7 @@ abstract protected function getClass(): string; protected function setUp(): void { - $this->abstractPlatform = $this->getMockForAbstractClass( - AbstractPlatform::class, - array(), - '', - true, - true, - true, - array('getVarcharTypeDeclarationSQL', 'getDecimalTypeDeclarationSQL') - ); + $this->abstractPlatform = self::createStub(AbstractPlatform::class); $class = $this->getClass(); $name = $class::NAME;