Skip to content

Instantly share code, notes, and snippets.

@nxvhm
Last active October 16, 2025 19:52
Show Gist options
  • Save nxvhm/f2cdd256f2fd8a3d0a6f19146492c3a1 to your computer and use it in GitHub Desktop.
Save nxvhm/f2cdd256f2fd8a3d0a6f19146492c3a1 to your computer and use it in GitHub Desktop.
PostgreSQL Enum Helper class for Laravel

Helper class to handle native PostgreSQL Enum types inside Laravel application.

<?php
  
$pgEnum = new PostgreEnum;
$pgEnum->setDebug(true);

# Create new enum type
$pgEnum->addType('user_status_type', ['Active', 'Inactive']);

# Explicitly Append new values to enum type 
$pgEnum->addValuesToType('user_status_type', ['Active', 'Inactive', 'Closed']);

/**
 * Append or Remove values to/from type
 * The method is a wrapper of addValuesToType if new Values detected.
 * If removed values are detected it will re-create the enum with the currently provided values
 * When removing values, dont forget to remove the referenced values inside DB
 **/
$pgEnum->addType('user_status_type', ['Active', 'Inactive', 'Blocked']);

# If an enum needs to be updated, but is being used by some table columns, provide the list of the affected
# Tables/columns before updating it, then call the addType method
$pgEnum->setTableColumns([
  'users' => ['status'],
  'table2' => ['col1', 'col2']
]);
$pgEnum->addType('user_status_type', ['Active', 'Inactive']);

# Get a list of all available enum types in database
$typesList = $pgEnum->getCustomTypes();

# Get Available Values for an Enum type together with it's coresponding oid
$defition = $pgEnum->getTypeDefinition('myType');

Using inside migration:

use App\Lib\Helpers\PostgreEnum;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
	public function up(): void {
    $pgEnum = new PostgreEnum;
    $pgEnum->setDebug(true);
    $pgEnum->addType('user_status_type', ['Active', 'Inactive']);
	Schema::create('users', function (Blueprint $table) {
		...
		$table->rawColumn('user_status_type', 'status');
		...
	});
  }

}
<?php
declare(strict_types=1);
namespace App\Lib\Helpers;
use Illuminate\Support\Facades\DB;
class PostgreEnum {
public bool $debug = false;
/**
* Affected Table's columns by enum migration
* Provide in format: tableName => [columnName1, columnName2]
* @var array
*/
public array $tableColumns = [];
public function setTableColumns(array $tableColumns = []): self {
$this->tableColumns = $tableColumns;
return $this;
}
public function setDebug(bool $debug): self {
$this->debug = $debug;
return $this;
}
private function printDebugMessage(string $msg): void {
echo $msg . PHP_EOL;
}
public function getCustomTypes(): ?array {
$res = DB::select("
SELECT
pg_type.typname AS enum,
pg_enum.enumlabel AS enumlabel
FROM pg_type
JOIN pg_enum ON pg_enum.enumtypid = pg_type.oid;
");
$res = collect($res);
$res = $res->groupBy('enum')->toArray();
if(empty($res))
return null;
$struct = [];
foreach($res as $enumName => $values) {
if(!isset($struct[$enumName]))
$struct[$enumName] = [];
foreach($values as $enumData) {
$struct[$enumName][] = $enumData->enumlabel;
}
}
return $struct;
}
public function hasType(string $typeName): bool {
$existance = current(DB::select("SELECT EXISTS (SELECT 1 FROM pg_type WHERE typname = ?) AS exist", [$typeName]));
return $existance->exist;
}
public function addType(string $typeName, array $values) {
if(!$this->hasType($typeName))
return $this->createType($typeName, $values);
$currentValues = self::getTypeDefinition($typeName);
$toRemove = array_diff($currentValues, $values);
$toAdd = array_diff($values, $currentValues);
# If values removed from the current definition, then we need to perform to migrate the enum
if(count($toRemove))
return $this->migrateType($typeName, $values);
# Add New values to enum
if($toAdd)
return $this->addValuesToType($typeName, $values);
}
public function createType(string $typeName, array $values) {
if($this->hasType($typeName))
return $this->debug ? $this->printDebugMessage(sprintf('Type %s already exists', $typeName)) : null;
$placeholders = implode(",", array_fill(0, count($values), "'%s'"));
$query = sprintf("CREATE TYPE %s AS ENUM(".$placeholders .")", $typeName, ...$values);
if($this->debug)
$this->printDebugMessage($query);
DB::unprepared($query);
if($this->debug)
$this->printDebugMessage(sprintf('Type %s created with values: %s', $typeName, implode(',', $values)));
}
public function migrateType(string $typeName, array $values): void {
# Alter affected tables/columns to be text values
foreach($this->tableColumns as $table => $columns) {
foreach($columns as $column) {
$columnToText = sprintf("ALTER TABLE %s ALTER %s TYPE text", $table, $column);
DB::unprepared($columnToText);
if($this->debug)
$this->printDebugMessage($columnToText);
}
}
# Drop the enum type
$dropTypeQuery = sprintf('DROP TYPE %s', $typeName);
DB::unprepared($dropTypeQuery);
if($this->debug)
$this->printDebugMessage($dropTypeQuery);
# Create again the enum type with the new values
$this->createType($typeName, $values);
# Alter the tables/columns to be the desired type
foreach($this->tableColumns as $table => $columns) {
foreach($columns as $column) {
$columnToEnum = sprintf("ALTER TABLE %s ALTER %s TYPE %s USING %s::%s;", $table, $column, $typeName, $column, $typeName);
DB::unprepared($columnToEnum);
if($this->debug)
$this->printDebugMessage($columnToEnum);
}
}
}
public function addValuesToType(string $typeName, array $toAdd) {
$currentValues = self::getTypeDefinition($typeName);
foreach($toAdd as $v) {
if(in_array($v, $currentValues))
continue;
DB::unprepared(sprintf('ALTER TYPE %s ADD VALUE \'%s\'', $typeName, $v));
}
}
public function getTypeDefinition(string $typeName): array {
$typeData = current(DB::select("SELECT * FROM pg_type WHERE typname = ?", [$typeName]));
if(empty($typeData))
return [];
return array_map(
fn($data) => $data->enumlabel,
DB::select("SELECT * FROM pg_enum WHERE enumtypid = ?", [$typeData->oid])
);
}
public function typeHasValue(string $typeName, string $value, array $enumValues = []): bool {
return in_array($value, empty($enumValues) ? self::getTypeDefinition($typeName) : $enumValues);
}
public function dropType(string $typeName) {
DB::unprepared(sprintf('DROP TYPE IF EXISTS %s', $typeName));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment