Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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: 6 additions & 0 deletions docs/AVAILABLE-TYPES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` |
50 changes: 41 additions & 9 deletions src/MartinGeorgiev/Doctrine/DBAL/Types/BaseArray.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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);
Expand Down
72 changes: 72 additions & 0 deletions src/MartinGeorgiev/Doctrine/DBAL/Types/BaseNetworkTypeArray.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

declare(strict_types=1);

namespace MartinGeorgiev\Doctrine\DBAL\Types;

use Doctrine\DBAL\Types\ConversionException;

/**
* Base class for network-related PostgreSQL array types (INET[], CIDR[], MACADDR[]).
*
* @since 3.0
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*/
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;
}
59 changes: 59 additions & 0 deletions src/MartinGeorgiev/Doctrine/DBAL/Types/Cidr.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

declare(strict_types=1);

namespace MartinGeorgiev\Doctrine\DBAL\Types;

use Doctrine\DBAL\Platforms\AbstractPlatform;
use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidCidrForDatabaseException;
use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidCidrForPHPException;
use MartinGeorgiev\Doctrine\DBAL\Types\Traits\CidrValidationTrait;

/**
* Implementation of PostgreSQL CIDR data type.
*
* @see https://www.postgresql.org/docs/current/datatype-net-types.html#DATATYPE-CIDR
* @since 3.0
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*/
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;
}
}
43 changes: 43 additions & 0 deletions src/MartinGeorgiev/Doctrine/DBAL/Types/CidrArray.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace MartinGeorgiev\Doctrine\DBAL\Types;

use MartinGeorgiev\Doctrine\DBAL\Types\Exceptions\InvalidCidrArrayItemForPHPException;
use MartinGeorgiev\Doctrine\DBAL\Types\Traits\CidrValidationTrait;

/**
* Implementation of PostgreSQL CIDR[] data type.
*
* @see https://www.postgresql.org/docs/current/datatype-net-types.html#DATATYPE-CIDR
* @since 3.0
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*/
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('');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace MartinGeorgiev\Doctrine\DBAL\Types\Exceptions;

use Doctrine\DBAL\Types\ConversionException;

/**
* @since 3.0
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*/
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace MartinGeorgiev\Doctrine\DBAL\Types\Exceptions;

use Doctrine\DBAL\Types\ConversionException;

/**
* @since 3.0
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*/
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace MartinGeorgiev\Doctrine\DBAL\Types\Exceptions;

use Doctrine\DBAL\Types\ConversionException;

/**
* @since 3.0
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*/
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

declare(strict_types=1);

namespace MartinGeorgiev\Doctrine\DBAL\Types\Exceptions;

use Doctrine\DBAL\Types\ConversionException;

/**
* @since 3.0
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*/
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);
}
}
Loading