Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
25 changes: 18 additions & 7 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
parameters:
paths:
- src/
- tests/

includes:
- vendor/assoconnect/php-quality-config/phpstan.extension.neon
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
2 changes: 1 addition & 1 deletion src/Doctrine/DBAL/Types/AbstractFixedLengthStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
52 changes: 39 additions & 13 deletions src/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsType.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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,
]);
}
}
11 changes: 7 additions & 4 deletions tests/Doctrine/DBAL/Types/BelgianEnterpriseNumberTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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));
}
}
4 changes: 2 additions & 2 deletions tests/Doctrine/DBAL/Types/BicTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
11 changes: 7 additions & 4 deletions tests/Doctrine/DBAL/Types/CountryTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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));
}
}
2 changes: 1 addition & 1 deletion tests/Doctrine/DBAL/Types/CurrencyTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 45 additions & 14 deletions tests/Doctrine/DBAL/Types/DateTimeImmutableMicroSecondsTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -60,26 +69,45 @@ 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);

self::assertSame($expectedDatetimeResult, $this->type->convertToDatabaseValue($date, $this->platform));
}

/**
* @dataProvider provideDateTimeValues
*/
#[DataProvider('provideDateTimes')]
public function testConvertDateToPHPValue(string $datetime): void
{
$date = new \DateTimeImmutable($datetime);

self::assertSame($date, $this->type->convertToPHPValue($date, $this->platform));
}

/**
* @return iterable<string, array{string}>
*/
public static function provideDateTimes(): iterable
{
foreach (self::provideDateTimeValues() as $key => [$datetime]) {
yield $key => [$datetime];
}
}


/**
* @return iterable<string, array{string, string}>
Expand All @@ -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'));
}

Expand All @@ -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'));
}

Expand Down
2 changes: 1 addition & 1 deletion tests/Doctrine/DBAL/Types/EmailTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
2 changes: 1 addition & 1 deletion tests/Doctrine/DBAL/Types/FrenchSiretTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);
}
}
2 changes: 1 addition & 1 deletion tests/Doctrine/DBAL/Types/LatitudeTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading