Arbeiten mit Enum-Typen in Symfony und PostgreSQL

Manchmal benötigen wir eine Liste mit gültigen Werten für ein bestimmtes Feld in der Datenbank. Zu diesem Zweck können wir einen benutzerdefinierten Datentyp in Symfony erstellen und ihn in unseren Entity Mapping Definitionen verwenden. So können wir ihn auch für unsere Validierungen nutzen. Dies ist besonders hilfreich bei der Verwendung von PostgreSQL-ENUM-Typen.

Dieses Verfahren ist für PHP-Versionen vor PHP 8.1 gedacht, die den nativen Enum-Typ enthalten. Wir könnten die gleiche Lösung mit wenigen Änderungen mit Enums oder mit einem anderen bestehenden Ansatz behandeln. Viele Projekte verwenden jedoch immer noch PHP 8.0 oder älter und dieser Code ist immer noch nützlich für die Anpassung unserer Enum-Typen in PostgreSQL mit Doctrine und Symfony.

Stell dir vor, du hast einen Typ appointment_status als Enum in deinem alten Datenbankschema wie folgt definiert.

CREATE TYPE public.appointment_status AS ENUM
 ('Open', 'Invited', 'Assigned', 'Unassigned');

Und du verwendest diesen Typ in einer bestimmten Spalte:

CREATE TABLE IF NOT EXISTS public.appointment
  {
 id integer NOT NULL,
 status public.appointment_status NOT NULL DEFAULT 'Open'::public.appointment_status,
 comment character varying(500)
  }

In diesem Artikel erfahren wir also, wie man diese Art von Daten auf einfache Weise handhaben kann.

Erstellen eines abstrakten Enum-Typs

Der erste Schritt ist die Erstellung einer abstrakten Klasse für diese Art von Daten. Mit dieser Abstraktion können wir die Klasse für jedes Enum-Feld wiederverwenden.

schema . ".\"" . $this->getName() . "\""; 
  }  
 
 public function convertToPHPValue($value, AbstractPlatform $platform) 
  {  
 return $value; 
  }  
 
 public function convertToDatabaseValue($value, AbstractPlatform $platform) 
  {  
 if (!in_array($value, $this->getValidValues())) { 
 throw new \InvalidArgumentException("Invalid '".$this->name."' value."); 
  }  
 return $value; 
  }  
 
 public function getName(): string 
  {  
 return $this->name; 
  }  
 
 public function requiresSQLCommentHint(AbstractPlatform $platform): bool 
  {  
 return true; 
  }  
 
 public function getValidValues(): array 
  {  
 return $this->values; 
  }  
 
}  
 

Erstellen des Enum-Typs

Nun musst du den spezifischen Enum-Typ erstellen, der die abstrakte Klasse erweitert:

values = self::$options; 
  }  
 
 public function getValidValues(): array 
  {  
 return self::$options; 
  }  
}  
 

Tipp: Wenn die Liste zu lang ist, kannst du die Klasse in zwei Dateien aufteilen; die Logik auf der einen Seite und die Werte auf der anderen, so erhältst du besser wartbare Klassen

values = CitizenshipEnum::getValues();
  }
 public function getValidValues(): array
  {
 return CitizenshipEnum::getValues();
  }
}
namespace App\Infrastructure\Persistence\Doctrine\DBAL\Enums;
class CitizenshipEnum
{
 protected static array $values = [
 'Ohne Angabe',
 'Afghanistan',
 'Algerien',
 'Andorra',
 'Angola',
 '[....]',
 'Vietnam',
 'Zentralafrikanische Republik',
 'Zypern',
 ];
 public static function getValues(): array
  {
 return self::$values;
  }
}

Definieren als Typ in Doctrine

An diesem Punkt müssen wir Symfony mitteilen, dass wir einen neuen Datentyp haben. Das einzige, was noch aussteht, ist eine Map in Doctrine und wir müssen ihr sagen „Eh, dieser Datentyp ist etwas Besonderes!“. Bearbeite die doctrine.yml und füge diese Zeilen hinzu:

doctrine: 
 dbal: 

 […] 
 mapping_types: 
 appointment_status: appointment_status 

 

 types: 
 appointment_status: App\..\AppointmentStatusType 

 

Mapping Entität mit benutzerdefiniertem Datentyp

Ja! Doctrine kennt jetzt die einzigen zulässigen Werte für den Status. In unserer XML-Datei für das Mapping von Entitäten können wir jetzt verwenden:

<!-- status -->
<field name="status" type="appointment_status" column="status">
    <options>
        <option name="default">Open</option>
    </options>
</field> 

In unserer Entitätsdatei müssen wir „string“ verwenden und uns nicht um den Datentyp kümmern… Doctrine macht alles für uns:

private string $status; 

Verwendung von Asserts zur Validierung von Eingaben

Wenn wir nun eine Eingabe mit einem speziellen Datentyp validieren wollen, können wir diese Einschränkung mit Symfony/Validator verwenden. Dies ist nützlich für die Validierung von Formularen oder POST-Aufrufen.

status: 
  - NotBlank: ~ 
  - Choice: { callback: [ \[..]ProductStatusType, getValidValues] }  

Und Symfony prüft, ob der angegebene Wert in den zugelassenen Werten in unserem benutzerdefinierten Datentyp definiert ist…. Zauberei! ie Callback-Definition ruft die Methode getValidValues() auf und prüft, ob der angegebene Wert ein gültiger Wert ist.

Symfony ist großartig!