How to add converters for new base types
Adding support for a new base type, either built-in or added by some Postgres extension, will require
a new custom TypeConverter implementation. Additionally, a class to represent values of that type on PHP
side will probably be needed.
Notably, pg_wrapper lacks converters for types like uuid and inet. The main reason is that properly
implementing PHP objects to support these types is way out of scope for the package, while using some external
implementation will introduce unnecessary dependencies. When you decide on the dependency you wish to use, it doesn’t
require much effort to create a converter, as shown below for uuid type.
Base classes for TypeConverter implementations
The package contains abstract BaseConverter and ContainerConverter classes, the new converter implementation
should probably extend one of these based on the properties of the type.
BaseConverter
This base class implements input() and output() methods that handle null values as pgsql extension itself
converts NULL fields of any type to PHP null values.
It delegates handling of non-null values to the two new abstract methods
inputNotNull(string $native): mixedConverts a string received from PostgreSQL to PHP variable of the proper type.
outputNotNull(mixed $value): stringReturns a string representation of PHP variable not identical to null.
ContainerConverter
This class extends BaseConverter and defines helper methods for parsing complex string representations.
Those accept the string received from the database and position of the current symbol that is updated
once parts of the string is processed.
nextChar(string $str, int &$p): ?stringGets next non-whitespace character from input, position is updated. Returns
nullif input ended.expectChar(string $string, int &$pos, string $char): voidThrows a
TypeConversionExceptionif next non-whitespace character in input is not the given char, moves to the next symbol otherwise.
inputNotNull() is implemented in ContainerConverter, a new abstract method is defined instead
parseInput(string $native, int &$pos): mixedParses a string representation into PHP variable from given position. This may be called from any position in the string and should return once it finishes parsing the value.
This can be used by converters of complex types to delegate parsing to converters of their subtypes, e.g.
MultiRangeConverterdelegates toRangeConverter,Converters of various geometric types delegate to
PointConverter.
Adding converter for uuid type
We will use the obvious choice for representing UUIDs on PHP side: ramsey/uuid package.
The converter class will extend BaseConverter as it will not actually parse UUIDs itself:
namespace sad_spirit\pg_wrapper\converters;
use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use sad_spirit\pg_wrapper\exceptions\TypeConversionException;
class UuidConverter extends BaseConverter
{
protected function inputNotNull(string $native): UuidInterface
{
return Uuid::fromString($native);
}
protected function outputNotNull($value): string
{
if ($value instanceof UuidInterface) {
return $value->toString();
} elseif (\is_string($value)) {
// We can validate a string here, but Postgres is a bit more lax with formats than ramsey/uuid
return $value;
}
throw TypeConversionException::unexpectedValue($this, 'output', 'a string or an implementation of UuidInterface', $value);
}
}
The classes should then be registered with DefaultTypeConverterFactory:
use Ramsey\Uuid\UuidInterface;
use sad_spirit\pg_wrapper\converters\UuidConverter;
$factory->registerConverter(UuidConverter::class, 'uuid');
$factory->registerClassMapping(UuidInterface::class, 'uuid');
The registerConverter() call makes $factory return an instance of UuidConverter when asked
for a converter for uuid type or its OID, so you’ll get implementations of UuidInterface instead of strings in
a query result.
Adding a mapping for UuidInterface via registerClassMapping() will allow using
implementations of that for query parameter values without the need to specify types.