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: 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