Trabajando con Enum Types en Symfony y PostgreSQL

A veces necesitamos una lista de valores válidos para un determinado campo de la base de datos. Para ello, podemos crear un tipo de datos personalizado en Symfony y utilizarlo en nuestras definiciones de mapeo de entidades. De este modo, también podemos utilizarlo en nuestras validaciones. Es especialmente útil cuando se utilizan tipos ENUM de PostgreSQL.

Este procedimiento está pensado para versiones de PHP anteriores a PHP 8.1 que incluyen el tipo nativo Enum. Podríamos aplicar la misma solución usando enums con unos pocos cambios o usando uno de los otros enfoques existentes. De todas formas, muchos proyectos siguen utilizando PHP 8.0 o anterior y este código sigue siendo útil para personalizar nuestros tipos enum en PostgreSQL con Doctrine y Symfony.

Imagina que has definido un tipo appointment_status como Enum en tu antiguo esquema de base de datos de la siguiente manera.

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

And you use this type in a certain column:

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

En este artículo aprenderemos a manejar este tipo de datos de forma sencilla.

Creación de un tipo Enum abstracto

El primer paso es crear una clase abstracta para este tipo de datos. Con esta abstracción, podemos reutilizar la clase para cualquier campo enum.

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

Creación del tipo Enum

Ahora debes seguir la creación del Tipo Enum específico extendiendo la Clase Abstracta:

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

Consejo: Si la lista es demasiado larga, puede dividir la clase en dos archivos; la lógica por un lado, y los valores por otro, y obtener clases más mantenibles

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

Definir como tipo en Doctrina

En este punto tenemos que decirle a Symfony que tenemos un nuevo tipo de datos. Así, lo único pendiente es mapearlo en doctrine y hay que decirle «¡Eh, este tipo de datos es especial!». Edite el doctrine.yml y añada estas líneas:

doctrine: 
 dbal: 

 […] 
 mapping_types: 
 appointment_status: appointment_status 

 

 types: 
 appointment_status: App\..\AppointmentStatusType 

 

Mapeo de entidad con tipo de datos personalizado

¡Sí! Doctrine conoce ahora los únicos valores admitidos para el estado. En nuestro archivo XML de mapeo de entidades, ahora podemos utilizar:

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

En nuestro archivo de entidad, debemos utilizar «string» y no preocuparnos por el tipo de datos… Doctrine lo hace todo por nosotros:

private string $status; 

Uso de assets para validar entradas

Si ahora queremos validar un input con un tipo de dato especial, como vimos antes, podemos usar esta restricción usando Symfony/Validator. Esto es útil para validar formularios o llamadas POST.

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

Y Symfony comprobará si el valor dado está definido en los valores admitidos en nuestro tipo de datos personalizado…. ¡Magia! La definición de callback hace una llamada al método getValidValues() y comprueba si el valor dado es válido.

¡Symfony es genial!