diff --git a/docs/AVAILABLE-TYPES.md b/docs/AVAILABLE-TYPES.md index c2889d1f..7fc38cd8 100644 --- a/docs/AVAILABLE-TYPES.md +++ b/docs/AVAILABLE-TYPES.md @@ -11,3 +11,9 @@ | text[] | _text | `MartinGeorgiev\Doctrine\DBAL\Types\TextArray` | | jsonb[] | _jsonb | `MartinGeorgiev\Doctrine\DBAL\Types\JsonbArray` | | jsonb | jsonb | `MartinGeorgiev\Doctrine\DBAL\Types\Jsonb` | +| inet | inet | `MartinGeorgiev\Doctrine\DBAL\Types\Inet` | +| inet[] | _inet | `MartinGeorgiev\Doctrine\DBAL\Types\InetArray` | +| cidr | cidr | `MartinGeorgiev\Doctrine\DBAL\Types\Cidr` | +| cidr[] | _cidr | `MartinGeorgiev\Doctrine\DBAL\Types\CidrArray` | +| macaddr | macaddr | `MartinGeorgiev\Doctrine\DBAL\Types\Macaddr` | +| macaddr[] | _macaddr | `MartinGeorgiev\Doctrine\DBAL\Types\MacaddrArray` | diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseArray.php index 079051b7..d42a7af1 100644 --- a/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseArray.php +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseArray.php @@ -31,16 +31,12 @@ public function convertToDatabaseValue($phpArray, AbstractPlatform $platform): ? } if (!\is_array($phpArray)) { - $exceptionMessage = 'Given PHP value content type is not PHP array. Instead it is "%s".'; - - throw new \InvalidArgumentException(\sprintf($exceptionMessage, \gettype($phpArray))); + $this->throwInvalidTypeException($phpArray); } foreach ($phpArray as &$item) { if (!$this->isValidArrayItemForDatabase($item)) { - $exceptionMessage = "One or more of the items given doesn't look valid."; - - throw new ConversionException($exceptionMessage); + $this->throwInvalidItemException(); } $item = $this->transformArrayItemForPostgres($item); @@ -49,6 +45,44 @@ public function convertToDatabaseValue($phpArray, AbstractPlatform $platform): ? return '{'.\implode(',', $phpArray).'}'; } + /** + * @throws \InvalidArgumentException + */ + protected function throwInvalidTypeException(mixed $value): never + { + throw $this->createInvalidTypeException($value); + } + + /** + * @throws ConversionException + */ + protected function throwInvalidItemException(): never + { + throw $this->createInvalidItemException(); + } + + /** + * @throws ConversionException + */ + protected function throwInvalidPostgresTypeException(mixed $value): never + { + throw new ConversionException( + \sprintf('Given PostgreSQL value content type is not PHP string. Instead it is "%s".', \gettype($value)) + ); + } + + protected function createInvalidTypeException(mixed $value): \InvalidArgumentException + { + return new \InvalidArgumentException( + \sprintf('Given PHP value content type is not PHP array. Instead it is "%s".', \gettype($value)) + ); + } + + protected function createInvalidItemException(): ConversionException + { + return new ConversionException("One or more of the items given doesn't look valid."); + } + /** * Tests if given PHP array item is from compatible type for PostgreSQL. */ @@ -79,9 +113,7 @@ public function convertToPHPValue($postgresArray, AbstractPlatform $platform): ? } if (!\is_string($postgresArray)) { - $exceptionMessage = 'Given PostgreSQL value content type is not PHP string. Instead it is "%s".'; - - throw new ConversionException(\sprintf($exceptionMessage, \gettype($postgresArray))); + $this->throwInvalidPostgresTypeException($postgresArray); } $phpArray = $this->transformPostgresArrayToPHPArray($postgresArray); diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArray.php new file mode 100644 index 00000000..eed91273 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArray.php @@ -0,0 +1,72 @@ + + */ +abstract class BaseNetworkTypeArray extends BaseArray +{ + /** + * Always quote network address values when transforming to PostgreSQL format. + */ + protected function transformArrayItemForPostgres(mixed $item): string + { + if ($item === null) { + return 'NULL'; + } + + if (!\is_string($item)) { + throw new ConversionException(\sprintf("Value %s can't be resolved to valid network array item", \var_export($item, true))); + } + + return '"'.$item.'"'; + } + + public function isValidArrayItemForDatabase(mixed $item): bool + { + if (!\is_string($item)) { + return false; + } + + return $this->isValidNetworkAddress($item); + } + + public function transformArrayItemForPHP(mixed $item): ?string + { + if ($item === null) { + return null; + } + + if (!\is_string($item)) { + $this->throwInvalidTypeException($item); + } + + // Remove surrounding quotes if present + $unquotedItem = \trim($item, '"'); + + if (!$this->isValidNetworkAddress($unquotedItem)) { + $this->throwInvalidFormatException($item); + } + + return $unquotedItem; + } + + /** + * Validate if the given string is a valid network address for this type. + */ + abstract protected function isValidNetworkAddress(string $value): bool; + + /** + * Get the exception to throw when the format is invalid. + */ + abstract protected function throwInvalidFormatException(mixed $value): \Exception; +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Cidr.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Cidr.php new file mode 100644 index 00000000..146a5166 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Cidr.php @@ -0,0 +1,59 @@ + + */ +class Cidr extends BaseType +{ + use CidrValidationTrait; + + protected const TYPE_NAME = 'cidr'; + + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + if ($value === null) { + return null; + } + + if (!\is_string($value)) { + throw InvalidCidrForPHPException::forInvalidType($value); + } + + if (!$this->isValidCidrAddress($value)) { + throw InvalidCidrForPHPException::forInvalidFormat($value); + } + + return $value; + } + + public function convertToPHPValue($value, AbstractPlatform $platform): ?string + { + if ($value === null) { + return null; + } + + if (!\is_string($value)) { + throw InvalidCidrForDatabaseException::forInvalidType($value); + } + + if (!$this->isValidCidrAddress($value)) { + throw InvalidCidrForDatabaseException::forInvalidFormat($value); + } + + return $value; + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/CidrArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/CidrArray.php new file mode 100644 index 00000000..ee3d31d2 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/CidrArray.php @@ -0,0 +1,43 @@ + + */ +class CidrArray extends BaseNetworkTypeArray +{ + use CidrValidationTrait; + + protected const TYPE_NAME = 'cidr[]'; + + protected function isValidNetworkAddress(string $value): bool + { + return $this->isValidCidrAddress($value); + } + + protected function throwInvalidTypeException(mixed $value): never + { + throw InvalidCidrArrayItemForPHPException::forInvalidType($value); + } + + protected function throwInvalidFormatException(mixed $value): never + { + throw InvalidCidrArrayItemForPHPException::forInvalidFormat($value); + } + + protected function throwInvalidItemException(): never + { + throw InvalidCidrArrayItemForPHPException::forInvalidFormat(''); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidCidrArrayItemForPHPException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidCidrArrayItemForPHPException.php new file mode 100644 index 00000000..6aee8a3a --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidCidrArrayItemForPHPException.php @@ -0,0 +1,35 @@ + + */ +class InvalidCidrArrayItemForPHPException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function forInvalidType(mixed $value): self + { + return self::create('Array values must be strings, %s given', $value); + } + + public static function forInvalidFormat(mixed $value): self + { + return self::create('Invalid CIDR address format in array: %s', $value); + } + + public static function forInvalidArrayType(mixed $value): self + { + return self::create('Value must be an array, %s given', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidCidrForDatabaseException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidCidrForDatabaseException.php new file mode 100644 index 00000000..fe42f8d4 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidCidrForDatabaseException.php @@ -0,0 +1,30 @@ + + */ +class InvalidCidrForDatabaseException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function forInvalidType(mixed $value): self + { + return self::create('Database value must be a string, %s given', $value); + } + + public static function forInvalidFormat(mixed $value): self + { + return self::create('Invalid CIDR address format in database: %s', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidCidrForPHPException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidCidrForPHPException.php new file mode 100644 index 00000000..4e349dd4 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidCidrForPHPException.php @@ -0,0 +1,30 @@ + + */ +class InvalidCidrForPHPException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function forInvalidType(mixed $value): self + { + return self::create('Value must be a string, %s given', $value); + } + + public static function forInvalidFormat(mixed $value): self + { + return self::create('Invalid CIDR address format: %s', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidInetArrayItemForPHPException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidInetArrayItemForPHPException.php new file mode 100644 index 00000000..0d3b9b49 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidInetArrayItemForPHPException.php @@ -0,0 +1,35 @@ + + */ +class InvalidInetArrayItemForPHPException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function forInvalidType(mixed $value): self + { + return self::create('Array values must be strings, %s given', $value); + } + + public static function forInvalidFormat(mixed $value): self + { + return self::create('Invalid INET address format in array: %s', $value); + } + + public static function forInvalidArrayType(mixed $value): self + { + return self::create('Value must be an array, %s given', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidInetForDatabaseException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidInetForDatabaseException.php new file mode 100644 index 00000000..d84dd715 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidInetForDatabaseException.php @@ -0,0 +1,30 @@ + + */ +class InvalidInetForDatabaseException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function forInvalidType(mixed $value): self + { + return self::create('Database value must be a string, %s given', $value); + } + + public static function forInvalidFormat(mixed $value): self + { + return self::create('Invalid INET address format in database: %s', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidInetForPHPException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidInetForPHPException.php new file mode 100644 index 00000000..7c8b04d6 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidInetForPHPException.php @@ -0,0 +1,30 @@ + + */ +class InvalidInetForPHPException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function forInvalidType(mixed $value): self + { + return self::create('Value must be a string, %s given', $value); + } + + public static function forInvalidFormat(mixed $value): self + { + return self::create('Invalid INET address format: %s', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidMacaddrArrayItemForPHPException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidMacaddrArrayItemForPHPException.php new file mode 100644 index 00000000..2abe9155 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidMacaddrArrayItemForPHPException.php @@ -0,0 +1,35 @@ + + */ +class InvalidMacaddrArrayItemForPHPException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function forInvalidType(mixed $value): self + { + return self::create('Array values must be strings, %s given', $value); + } + + public static function forInvalidFormat(mixed $value): self + { + return self::create('Invalid MAC address format in array: %s', $value); + } + + public static function forInvalidArrayType(mixed $value): self + { + return self::create('Value must be an array, %s given', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidMacaddrForDatabaseException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidMacaddrForDatabaseException.php new file mode 100644 index 00000000..77f0f350 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidMacaddrForDatabaseException.php @@ -0,0 +1,30 @@ + + */ +class InvalidMacaddrForDatabaseException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function forInvalidType(mixed $value): self + { + return self::create('Database value must be a string, %s given', $value); + } + + public static function forInvalidFormat(mixed $value): self + { + return self::create('Invalid MAC address format in database: %s', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidMacaddrForPHPException.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidMacaddrForPHPException.php new file mode 100644 index 00000000..99b4b35d --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Exceptions/InvalidMacaddrForPHPException.php @@ -0,0 +1,30 @@ + + */ +class InvalidMacaddrForPHPException extends ConversionException +{ + private static function create(string $message, mixed $value): self + { + return new self(\sprintf($message, \var_export($value, true))); + } + + public static function forInvalidType(mixed $value): self + { + return self::create('Value must be a string, %s given', $value); + } + + public static function forInvalidFormat(mixed $value): self + { + return self::create('Invalid MAC address format: %s', $value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Inet.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Inet.php new file mode 100644 index 00000000..8888506b --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Inet.php @@ -0,0 +1,59 @@ + + */ +class Inet extends BaseType +{ + use InetValidationTrait; + + protected const TYPE_NAME = 'inet'; + + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + if ($value === null) { + return null; + } + + if (!\is_string($value)) { + throw InvalidInetForPHPException::forInvalidType($value); + } + + if (!$this->isValidInetAddress($value)) { + throw InvalidInetForPHPException::forInvalidFormat($value); + } + + return $value; + } + + public function convertToPHPValue($value, AbstractPlatform $platform): ?string + { + if ($value === null) { + return null; + } + + if (!\is_string($value)) { + throw InvalidInetForDatabaseException::forInvalidType($value); + } + + if (!$this->isValidInetAddress($value)) { + throw InvalidInetForDatabaseException::forInvalidFormat($value); + } + + return $value; + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/InetArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/InetArray.php new file mode 100644 index 00000000..5a463beb --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/InetArray.php @@ -0,0 +1,43 @@ + + */ +class InetArray extends BaseNetworkTypeArray +{ + use InetValidationTrait; + + protected const TYPE_NAME = 'inet[]'; + + protected function isValidNetworkAddress(string $value): bool + { + return $this->isValidInetAddress($value); + } + + protected function throwInvalidTypeException(mixed $value): never + { + throw InvalidInetArrayItemForPHPException::forInvalidType($value); + } + + protected function throwInvalidFormatException(mixed $value): never + { + throw InvalidInetArrayItemForPHPException::forInvalidFormat($value); + } + + protected function throwInvalidItemException(): never + { + throw InvalidInetArrayItemForPHPException::forInvalidFormat(''); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Macaddr.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Macaddr.php new file mode 100644 index 00000000..95222732 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Macaddr.php @@ -0,0 +1,59 @@ + + */ +class Macaddr extends BaseType +{ + use MacaddrValidationTrait; + + protected const TYPE_NAME = 'macaddr'; + + public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string + { + if ($value === null) { + return null; + } + + if (!\is_string($value)) { + throw InvalidMacaddrForPHPException::forInvalidType($value); + } + + if (!$this->isValidMacAddress($value)) { + throw InvalidMacaddrForPHPException::forInvalidFormat($value); + } + + return $this->normalizeFormat($value); + } + + public function convertToPHPValue($value, AbstractPlatform $platform): ?string + { + if ($value === null) { + return null; + } + + if (!\is_string($value)) { + throw InvalidMacaddrForDatabaseException::forInvalidType($value); + } + + if (!$this->isValidMacAddress($value)) { + throw InvalidMacaddrForDatabaseException::forInvalidFormat($value); + } + + return $value; + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/MacaddrArray.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/MacaddrArray.php new file mode 100644 index 00000000..3565796f --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/MacaddrArray.php @@ -0,0 +1,47 @@ + + */ +class MacaddrArray extends BaseNetworkTypeArray +{ + protected const TYPE_NAME = 'macaddr[]'; + + protected function isValidNetworkAddress(string $value): bool + { + // Check if it's using colons consistently + if (\preg_match('/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/', $value)) { + return true; + } + + // Check if it's using hyphens consistently + // PostgreSQL requires MAC addresses to have separators + return (bool) \preg_match('/^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$/', $value); + } + + protected function throwInvalidTypeException(mixed $value): never + { + throw InvalidMacaddrArrayItemForPHPException::forInvalidType($value); + } + + protected function throwInvalidFormatException(mixed $value): never + { + throw InvalidMacaddrArrayItemForPHPException::forInvalidFormat($value); + } + + protected function throwInvalidItemException(): never + { + throw InvalidMacaddrArrayItemForPHPException::forInvalidFormat(''); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Traits/CidrValidationTrait.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Traits/CidrValidationTrait.php new file mode 100644 index 00000000..670ba26c --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Traits/CidrValidationTrait.php @@ -0,0 +1,20 @@ +isValidNetworkAddressWithCidr($value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Traits/InetValidationTrait.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Traits/InetValidationTrait.php new file mode 100644 index 00000000..146eb976 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Traits/InetValidationTrait.php @@ -0,0 +1,26 @@ +isValidPlainIpAddress($value)) { + return true; + } + + // Validate CIDR notation + return $this->isValidNetworkAddressWithCidr($value); + } +} diff --git a/src/MartinGeorgiev/Doctrine/DBAL/Types/Traits/MacaddrValidationTrait.php b/src/MartinGeorgiev/Doctrine/DBAL/Types/Traits/MacaddrValidationTrait.php new file mode 100644 index 00000000..ccb25f82 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/DBAL/Types/Traits/MacaddrValidationTrait.php @@ -0,0 +1,38 @@ +isValidIpv4($value)) { + return true; + } + + return $this->isValidIpv6($value); + } + + protected function isValidNetworkAddressWithCidr(string $value): bool + { + if (!$this->hasValidCidrFormat($value)) { + return false; + } + + [$ip, $netmask] = \explode('/', $value); + $netmask = (int) $netmask; + + if ($this->isValidIpv4($ip)) { + return $this->isValidIpv4Netmask($netmask); + } + + if ($this->isValidIpv6($ip)) { + return $this->isValidIpv6Netmask($netmask); + } + + return false; + } + + private function isValidIpv4(string $value): bool + { + // First remove any leading zeros from octets + // IPv4 addresses with leading zeros (like '192.168.001.001') are valid according to PostgreSQL's INET type specification + $normalized = \preg_replace('/\b0+(\d+)\b/', '$1', $value); + + return (bool) \filter_var($normalized, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); + } + + private function isValidIpv6(string $value): bool + { + return (bool) \filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); + } + + private function hasValidCidrFormat(string $value): bool + { + if (!\str_contains($value, '/')) { + return false; + } + + [$ip, $netmask] = \explode('/', $value); + + return \is_numeric($netmask); + } + + private function isValidIpv4Netmask(int $netmask): bool + { + return $netmask >= 0 && $netmask <= 32; + } + + private function isValidIpv6Netmask(int $netmask): bool + { + return $netmask >= 0 && $netmask <= 128; + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArrayTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArrayTest.php new file mode 100644 index 00000000..5c89c541 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArrayTest.php @@ -0,0 +1,121 @@ +platform = $this->createMock(AbstractPlatform::class); + // Create a concrete implementation of the abstract class for testing + $this->fixture = new class extends BaseNetworkTypeArray { + protected const TYPE_NAME = 'test_network_array'; + + protected function isValidNetworkAddress(string $value): bool + { + return $value === 'valid_address'; + } + + protected function throwInvalidTypeException(mixed $value): never + { + throw new \InvalidArgumentException('Invalid type'); + } + + protected function throwInvalidFormatException(mixed $value): never + { + throw new \InvalidArgumentException('Invalid format'); + } + + protected function throwInvalidItemException(): never + { + throw new \InvalidArgumentException('Invalid item'); + } + }; + } + + /** + * @test + */ + public function has_name(): void + { + self::assertEquals('test_network_array', $this->fixture->getName()); + } + + /** + * @test + */ + public function can_convert_null_to_database_value(): void + { + self::assertNull($this->fixture->convertToDatabaseValue(null, $this->platform)); + } + + /** + * @test + */ + public function can_convert_empty_array_to_database_value(): void + { + self::assertEquals('{}', $this->fixture->convertToDatabaseValue([], $this->platform)); + } + + /** + * @test + */ + public function can_convert_valid_array_to_database_value(): void + { + self::assertEquals( + '{"valid_address","valid_address"}', + $this->fixture->convertToDatabaseValue(['valid_address', 'valid_address'], $this->platform) + ); + } + + /** + * @test + */ + public function throws_exception_for_invalid_type(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid type'); + $this->fixture->convertToDatabaseValue('not_an_array', $this->platform); + } + + /** + * @test + */ + public function can_convert_null_to_php_value(): void + { + self::assertNull($this->fixture->convertToPHPValue(null, $this->platform)); + } + + /** + * @test + */ + public function can_convert_empty_array_to_php_value(): void + { + self::assertEquals([], $this->fixture->convertToPHPValue('{}', $this->platform)); + } + + /** + * @test + */ + public function can_convert_valid_string_to_php_value(): void + { + self::assertEquals( + ['valid_address', 'valid_address'], + $this->fixture->convertToPHPValue('{"valid_address","valid_address"}', $this->platform) + ); + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/CidrArrayTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/CidrArrayTest.php new file mode 100644 index 00000000..0f2206d5 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/CidrArrayTest.php @@ -0,0 +1,114 @@ +platform = $this->createMock(AbstractPlatform::class); + $this->fixture = new CidrArray(); + } + + /** + * @test + */ + public function has_name(): void + { + self::assertEquals('cidr[]', $this->fixture->getName()); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_from_php_value(?array $phpValue, ?string $postgresValue): void + { + self::assertEquals($postgresValue, $this->fixture->convertToDatabaseValue($phpValue, $this->platform)); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_to_php_value(?array $phpValue, ?string $postgresValue): void + { + self::assertEquals($phpValue, $this->fixture->convertToPHPValue($postgresValue, $this->platform)); + } + + /** + * @return array + */ + public static function provideValidTransformations(): array + { + return [ + 'null' => [ + 'phpValue' => null, + 'postgresValue' => null, + ], + 'empty array' => [ + 'phpValue' => [], + 'postgresValue' => '{}', + ], + 'IPv4 CIDR array' => [ + 'phpValue' => ['192.168.0.0/24', '10.0.0.0/8'], + 'postgresValue' => '{"192.168.0.0/24","10.0.0.0/8"}', + ], + 'IPv6 CIDR array' => [ + 'phpValue' => ['2001:db8::/32', 'fe80::/10'], + 'postgresValue' => '{"2001:db8::/32","fe80::/10"}', + ], + 'mixed CIDR array' => [ + 'phpValue' => ['192.168.1.0/24', '2001:db8::/32'], + 'postgresValue' => '{"192.168.1.0/24","2001:db8::/32"}', + ], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidTransformations + */ + public function throws_exception_when_invalid_data_provided_to_convert_to_database_value(mixed $phpValue): void + { + $this->expectException(InvalidCidrArrayItemForPHPException::class); + $this->fixture->convertToDatabaseValue($phpValue, $this->platform); // @phpstan-ignore-line + } + + /** + * @return array + */ + public static function provideInvalidTransformations(): array + { + return [ + 'invalid type' => ['not-an-array'], + 'invalid IPv4' => [['256.256.256.0/24']], + 'invalid IPv6' => [['2001:xyz::/32']], + 'missing netmask' => [['192.168.1.0']], + 'invalid netmask IPv4' => [['192.168.1.0/33']], + 'invalid netmask IPv6' => [['2001:db8::/129']], + 'mixed valid and invalid' => [['192.168.1.0/24', '256.256.256.0/24']], // Mixed valid/invalid values + 'empty string' => [['']], // Empty string in array + 'whitespace only' => [[' ']], // Whitespace string in array + 'malformed CIDR with spaces' => [['192.168.1.0 / 24']], // Space in CIDR notation + ]; + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/CidrTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/CidrTest.php new file mode 100644 index 00000000..744b1ec2 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/CidrTest.php @@ -0,0 +1,157 @@ +platform = $this->createMock(AbstractPlatform::class); + $this->fixture = new Cidr(); + } + + /** + * @test + */ + public function has_name(): void + { + self::assertEquals('cidr', $this->fixture->getName()); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_from_php_value(?string $phpValue, ?string $postgresValue): void + { + self::assertEquals($postgresValue, $this->fixture->convertToDatabaseValue($phpValue, $this->platform)); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_to_php_value(?string $phpValue, ?string $postgresValue): void + { + self::assertEquals($phpValue, $this->fixture->convertToPHPValue($postgresValue, $this->platform)); + } + + /** + * @return array + */ + public static function provideValidTransformations(): array + { + return [ + 'null' => [ + 'phpValue' => null, + 'postgresValue' => null, + ], + 'IPv4 CIDR' => [ + 'phpValue' => '192.168.0.0/24', + 'postgresValue' => '192.168.0.0/24', + ], + 'IPv4 CIDR with min netmask' => [ + 'phpValue' => '10.0.0.0/0', + 'postgresValue' => '10.0.0.0/0', + ], + 'IPv4 CIDR with max netmask' => [ + 'phpValue' => '172.16.0.0/32', + 'postgresValue' => '172.16.0.0/32', + ], + 'IPv6 CIDR' => [ + 'phpValue' => '2001:db8::/32', + 'postgresValue' => '2001:db8::/32', + ], + 'IPv6 CIDR with min netmask' => [ + 'phpValue' => 'fe80::/0', + 'postgresValue' => 'fe80::/0', + ], + 'IPv6 CIDR with max netmask' => [ + 'phpValue' => '2001:db8::/128', + 'postgresValue' => '2001:db8::/128', + ], + 'IPv6 CIDR uppercase' => [ + 'phpValue' => '2001:DB8::/32', + 'postgresValue' => '2001:DB8::/32', + ], + 'IPv6 CIDR full notation' => [ + 'phpValue' => '2001:0db8:0000:0000:0000:0000:0000:0000/32', + 'postgresValue' => '2001:0db8:0000:0000:0000:0000:0000:0000/32', + ], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidTransformations + */ + public function throws_exception_when_invalid_data_provided_to_convert_to_database_value(mixed $phpValue): void + { + $this->expectException(InvalidCidrForPHPException::class); + $this->fixture->convertToDatabaseValue($phpValue, $this->platform); + } + + /** + * @return array + */ + public static function provideInvalidTransformations(): array + { + return [ + 'empty string' => [''], + 'whitespace string' => [' '], + 'plain IPv4' => ['192.168.0.1'], + 'plain IPv6' => ['2001:db8::1'], + 'invalid type' => [123], + 'malformed CIDR with spaces' => ['192.168.0.0 / 24'], + 'invalid IPv4 address' => ['256.256.256.0/24'], + 'invalid IPv6 address' => ['2001:xyz::/32'], + 'IPv4 invalid netmask low' => ['192.168.0.0/-1'], + 'IPv4 invalid netmask high' => ['192.168.0.0/33'], + 'IPv6 invalid netmask low' => ['2001:db8::/-1'], + 'IPv6 invalid netmask high' => ['2001:db8::/129'], + 'CIDR without netmask' => ['192.168.0.0/'], + 'CIDR with non-numeric netmask' => ['192.168.0.0/xyz'], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidDatabaseValues + */ + public function throws_exception_when_invalid_data_provided_to_convert_to_php_value(mixed $dbValue): void + { + $this->expectException(InvalidCidrForDatabaseException::class); + $this->fixture->convertToPHPValue($dbValue, $this->platform); + } + + /** + * @return array + */ + public static function provideInvalidDatabaseValues(): array + { + return [ + 'invalid type' => [123], + 'invalid format from database' => ['invalid-cidr-format'], + ]; + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/InetArrayTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/InetArrayTest.php new file mode 100644 index 00000000..fabbf1c8 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/InetArrayTest.php @@ -0,0 +1,139 @@ +platform = $this->createMock(AbstractPlatform::class); + $this->fixture = new InetArray(); + } + + /** + * @test + */ + public function has_name(): void + { + self::assertEquals('inet[]', $this->fixture->getName()); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_from_php_value(?array $phpValue, ?string $postgresValue): void + { + self::assertEquals($postgresValue, $this->fixture->convertToDatabaseValue($phpValue, $this->platform)); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_to_php_value(?array $phpValue, ?string $postgresValue): void + { + self::assertEquals($phpValue, $this->fixture->convertToPHPValue($postgresValue, $this->platform)); + } + + /** + * @return array + */ + public static function provideValidTransformations(): array + { + return [ + 'null' => [ + 'phpValue' => null, + 'postgresValue' => null, + ], + 'empty array' => [ + 'phpValue' => [], + 'postgresValue' => '{}', + ], + 'IPv4 addresses' => [ + 'phpValue' => ['192.168.0.1', '10.0.0.1'], + 'postgresValue' => '{"192.168.0.1","10.0.0.1"}', + ], + 'IPv4 with CIDR' => [ + 'phpValue' => ['192.168.0.0/24', '10.0.0.0/8'], + 'postgresValue' => '{"192.168.0.0/24","10.0.0.0/8"}', + ], + 'IPv6 addresses' => [ + 'phpValue' => ['2001:db8::1', 'fe80::1'], + 'postgresValue' => '{"2001:db8::1","fe80::1"}', + ], + 'IPv6 with CIDR' => [ + 'phpValue' => ['2001:db8::/32', 'fe80::/10'], + 'postgresValue' => '{"2001:db8::/32","fe80::/10"}', + ], + 'mixed addresses' => [ + 'phpValue' => ['192.168.1.1', '2001:db8::1'], + 'postgresValue' => '{"192.168.1.1","2001:db8::1"}', + ], + 'mixed with CIDR' => [ + 'phpValue' => ['192.168.0.0/24', '2001:db8::/32'], + 'postgresValue' => '{"192.168.0.0/24","2001:db8::/32"}', + ], + 'uppercase IPv6' => [ + 'phpValue' => ['2001:DB8::1', 'FE80::1'], + 'postgresValue' => '{"2001:DB8::1","FE80::1"}', + ], + 'IPv6 full notation' => [ + 'phpValue' => ['2001:0db8:0000:0000:0000:0000:0000:0001'], + 'postgresValue' => '{"2001:0db8:0000:0000:0000:0000:0000:0001"}', + ], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidTransformations + */ + public function throws_exception_when_invalid_data_provided_to_convert_to_database_value(mixed $phpValue): void + { + $this->expectException(InvalidInetArrayItemForPHPException::class); + $this->fixture->convertToDatabaseValue($phpValue, $this->platform); // @phpstan-ignore-line + } + + /** + * @return array + */ + public static function provideInvalidTransformations(): array + { + return [ + 'invalid type' => ['not-an-array'], + 'invalid IPv4' => [['256.256.256.256']], + 'invalid IPv6' => [['2001:xyz::1']], + 'invalid CIDR format' => [['192.168.1.0/xyz']], + 'invalid IPv4 CIDR netmask' => [['192.168.1.0/33']], + 'invalid IPv6 CIDR netmask' => [['2001:db8::/129']], + 'wrong format' => [['not-an-ip-address']], + 'incomplete IPv4' => [['192.168.1']], + 'incomplete IPv6' => [['2001:db8']], + 'mixed valid and invalid' => [['192.168.1.1', 'invalid-ip']], + 'empty string' => [['']], + 'whitespace only' => [[' ']], + 'malformed CIDR with spaces' => [['192.168.1.0 / 24']], + 'IPv6 with invalid segment count' => [['2001:db8:1:2:3:4:5:6:7']], + 'IPv6 with invalid segment length' => [['2001:db8:xyz:1:1:1:1:1']], + ]; + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/InetTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/InetTest.php new file mode 100644 index 00000000..cbdd6ef7 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/InetTest.php @@ -0,0 +1,142 @@ +platform = $this->createMock(AbstractPlatform::class); + $this->fixture = new Inet(); + } + + /** + * @test + */ + public function has_name(): void + { + self::assertEquals('inet', $this->fixture->getName()); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_from_php_value(?string $phpValue, ?string $postgresValue): void + { + self::assertEquals($postgresValue, $this->fixture->convertToDatabaseValue($phpValue, $this->platform)); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_to_php_value(?string $phpValue, ?string $postgresValue): void + { + self::assertEquals($phpValue, $this->fixture->convertToPHPValue($postgresValue, $this->platform)); + } + + /** + * @return array + */ + public static function provideValidTransformations(): array + { + return [ + 'null' => [ + 'phpValue' => null, + 'postgresValue' => null, + ], + 'IPv4 address' => [ + 'phpValue' => '192.168.0.1', + 'postgresValue' => '192.168.0.1', + ], + 'IPv4 with CIDR' => [ + 'phpValue' => '192.168.0.0/24', + 'postgresValue' => '192.168.0.0/24', + ], + 'IPv6 address' => [ + 'phpValue' => '2001:db8::1', + 'postgresValue' => '2001:db8::1', + ], + 'IPv6 with CIDR' => [ + 'phpValue' => '2001:db8::/32', + 'postgresValue' => '2001:db8::/32', + ], + 'IPv4 loopback' => [ + 'phpValue' => '127.0.0.1', + 'postgresValue' => '127.0.0.1', + ], + 'IPv6 loopback' => [ + 'phpValue' => '::1', + 'postgresValue' => '::1', + ], + 'IPv4 with leading zeros' => [ + 'phpValue' => '192.168.001.001', + 'postgresValue' => '192.168.001.001', + ], + 'IPv6 compressed zeros' => [ + 'phpValue' => '2001:db8::1', + 'postgresValue' => '2001:db8::1', + ], + 'IPv6 full notation' => [ + 'phpValue' => '2001:0db8:0000:0000:0000:0000:0000:0001', + 'postgresValue' => '2001:0db8:0000:0000:0000:0000:0000:0001', + ], + 'IPv4-mapped IPv6' => [ + 'phpValue' => '::ffff:192.168.1.1', + 'postgresValue' => '::ffff:192.168.1.1', + ], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidTransformations + */ + public function throws_exception_when_invalid_data_provided_to_convert_to_database_value(mixed $phpValue): void + { + $this->expectException(InvalidInetForPHPException::class); + $this->fixture->convertToDatabaseValue($phpValue, $this->platform); + } + + /** + * @return array + */ + public static function provideInvalidTransformations(): array + { + return [ + 'empty string' => [''], + 'invalid IPv4' => ['256.256.256.256'], + 'invalid IPv6' => ['2001:xyz::1'], + 'invalid CIDR format' => ['192.168.1.0/xyz'], + 'invalid IPv4 CIDR netmask' => ['192.168.1.0/33'], + 'invalid IPv6 CIDR netmask' => ['2001:db8::/129'], + 'wrong format' => ['not-an-ip-address'], + 'incomplete IPv4' => ['192.168.1'], + 'incomplete IPv6' => ['2001:db8'], + 'IPv6 too many segments' => ['2001:db8:1:2:3:4:5:6:7'], + 'IPv6 invalid segment length' => ['2001:db8:xyz:1:1:1:1:1'], + 'IPv4 with invalid octet count' => ['192.168.1'], + 'IPv4 with character suffix' => ['192.168.1.1x'], + 'malformed IPv4-mapped IPv6' => ['::ffff:256.256.256.256'], + ]; + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/MacaddrArrayTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/MacaddrArrayTest.php new file mode 100644 index 00000000..3a154fa3 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/MacaddrArrayTest.php @@ -0,0 +1,120 @@ +platform = $this->createMock(AbstractPlatform::class); + $this->fixture = new MacaddrArray(); + } + + /** + * @test + */ + public function has_name(): void + { + self::assertEquals('macaddr[]', $this->fixture->getName()); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_from_php_value(?array $phpValue, ?string $postgresValue): void + { + self::assertEquals($postgresValue, $this->fixture->convertToDatabaseValue($phpValue, $this->platform)); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_to_php_value(?array $phpValue, ?string $postgresValue): void + { + self::assertEquals($phpValue, $this->fixture->convertToPHPValue($postgresValue, $this->platform)); + } + + /** + * @return array + */ + public static function provideValidTransformations(): array + { + return [ + 'null' => [ + 'phpValue' => null, + 'postgresValue' => null, + ], + 'empty array' => [ + 'phpValue' => [], + 'postgresValue' => '{}', + ], + 'colon-separated MAC addresses' => [ + 'phpValue' => ['08:00:2b:01:02:03', '00:0c:29:aa:bb:cc'], + 'postgresValue' => '{"08:00:2b:01:02:03","00:0c:29:aa:bb:cc"}', + ], + 'hyphen-separated MAC addresses' => [ + 'phpValue' => ['08-00-2b-01-02-03', '00-0c-29-aa-bb-cc'], + 'postgresValue' => '{"08-00-2b-01-02-03","00-0c-29-aa-bb-cc"}', + ], + 'mixed separator MAC addresses' => [ + 'phpValue' => ['08:00:2b:01:02:03', '00-0c-29-aa-bb-cc'], + 'postgresValue' => '{"08:00:2b:01:02:03","00-0c-29-aa-bb-cc"}', + ], + 'uppercase MAC addresses' => [ + 'phpValue' => ['08:00:2B:01:02:03', '00:0C:29:AA:BB:CC'], + 'postgresValue' => '{"08:00:2B:01:02:03","00:0C:29:AA:BB:CC"}', + ], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidTransformations + */ + public function throws_exception_when_invalid_data_provided_to_convert_to_database_value(mixed $phpValue): void + { + $this->expectException(InvalidMacaddrArrayItemForPHPException::class); + $this->fixture->convertToDatabaseValue($phpValue, $this->platform); // @phpstan-ignore-line + } + + /** + * @return array + */ + public static function provideInvalidTransformations(): array + { + return [ + 'invalid type' => ['not-an-array'], + 'invalid MAC format' => [['00:11:22:33:44:ZZ']], + 'too short' => [['00:11:22:33:44']], + 'too long' => [['00:11:22:33:44:55:66']], + 'no separators' => [['000011223344']], + 'wrong separator' => [['00.11.22.33.44.55']], + 'non-hex characters' => [['GG:HH:II:JJ:KK:LL']], + 'mixed valid and invalid' => [['08:00:2b:01:02:03', 'invalid-mac']], + 'empty string' => [['']], + 'whitespace only' => [[' ']], + 'malformed with spaces' => [['08:00 :2b:01:02:03']], + 'mixed case invalid chars' => [['GG:hh:II:jj:KK:ll']], + ]; + } +} diff --git a/tests/MartinGeorgiev/Doctrine/DBAL/Types/MacaddrTest.php b/tests/MartinGeorgiev/Doctrine/DBAL/Types/MacaddrTest.php new file mode 100644 index 00000000..49cfcaa0 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/DBAL/Types/MacaddrTest.php @@ -0,0 +1,152 @@ +platform = $this->createMock(AbstractPlatform::class); + $this->fixture = new Macaddr(); + } + + /** + * @test + */ + public function has_name(): void + { + self::assertEquals('macaddr', $this->fixture->getName()); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_from_php_value(?string $phpInput, ?string $postgresValueAfterNormalisation, ?string $phpValueAfterRetrievalFromDatabase): void + { + self::assertEquals($postgresValueAfterNormalisation, $this->fixture->convertToDatabaseValue($phpInput, $this->platform)); + } + + /** + * @test + * + * @dataProvider provideValidTransformations + */ + public function can_transform_to_php_value(?string $phpInput, ?string $postgresValueAfterNormalisation, ?string $phpValueAfterRetrievalFromDatabase): void + { + self::assertEquals($phpValueAfterRetrievalFromDatabase, $this->fixture->convertToPHPValue($postgresValueAfterNormalisation, $this->platform)); + } + + /** + * @return array + */ + public static function provideValidTransformations(): array + { + return [ + 'null' => [ + 'phpInput' => null, + 'postgresValueAfterNormalisation' => null, + 'phpValueAfterRetrievalFromDatabase' => null, + ], + 'colon-separated lowercase' => [ + 'phpInput' => '08:00:2b:01:02:03', + 'postgresValueAfterNormalisation' => '08:00:2b:01:02:03', + 'phpValueAfterRetrievalFromDatabase' => '08:00:2b:01:02:03', + ], + 'colon-separated uppercase' => [ + 'phpInput' => '08:00:2B:01:02:03', + 'postgresValueAfterNormalisation' => '08:00:2b:01:02:03', + 'phpValueAfterRetrievalFromDatabase' => '08:00:2b:01:02:03', + ], + 'hyphen-separated lowercase' => [ + 'phpInput' => '08-00-2b-01-02-03', + 'postgresValueAfterNormalisation' => '08:00:2b:01:02:03', + 'phpValueAfterRetrievalFromDatabase' => '08:00:2b:01:02:03', + ], + 'hyphen-separated uppercase' => [ + 'phpInput' => '08-00-2B-01-02-03', + 'postgresValueAfterNormalisation' => '08:00:2b:01:02:03', + 'phpValueAfterRetrievalFromDatabase' => '08:00:2b:01:02:03', + ], + 'no separator' => [ + 'phpInput' => '08002B010203', + 'postgresValueAfterNormalisation' => '08:00:2b:01:02:03', + 'phpValueAfterRetrievalFromDatabase' => '08:00:2b:01:02:03', + ], + 'mixed case no separator' => [ + 'phpInput' => '08002b010203', + 'postgresValueAfterNormalisation' => '08:00:2b:01:02:03', + 'phpValueAfterRetrievalFromDatabase' => '08:00:2b:01:02:03', + ], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidTransformations + */ + public function throws_exception_when_invalid_data_provided_to_convert_to_database_value(mixed $phpValue): void + { + $this->expectException(InvalidMacaddrForPHPException::class); + $this->fixture->convertToDatabaseValue($phpValue, $this->platform); + } + + /** + * @return array + */ + public static function provideInvalidTransformations(): array + { + return [ + 'empty string' => [''], + 'invalid format' => ['00:11:22:33:44'], + 'invalid characters' => ['00:11:22:gg:hh:ii'], + 'too short' => ['00:11:22:33:44'], + 'too long' => ['00:11:22:33:44:55:66'], + 'invalid separator' => ['00.11.22.33.44.55'], + 'non-hex characters' => ['GG:HH:II:JJ:KK:LL'], + 'wrong format' => ['not-a-mac-address'], + 'mixed separators' => ['00:11-22:33-44:55'], + 'non-hex digits' => ['zz:zz:zz:zz:zz:zz'], + ]; + } + + /** + * @test + * + * @dataProvider provideInvalidDatabaseValues + */ + public function throws_exception_when_invalid_data_provided_to_convert_to_php_value(mixed $dbValue): void + { + $this->expectException(InvalidMacaddrForDatabaseException::class); + $this->fixture->convertToPHPValue($dbValue, $this->platform); + } + + /** + * @return array + */ + public static function provideInvalidDatabaseValues(): array + { + return [ + 'invalid type' => [123], + 'invalid format from database' => ['invalid-mac-format'], + ]; + } +}