|  | <?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)); | 
        
          |  | } | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  |  | 
        
          |  | } |