Enum PHP-ben?
Sok dolgot hiányolok PHP-ből és az egyik ilyen az enum. Az enum egy olyan típus, amelyben előre definiált konstansok vannak és minden ilyen konstans egy objektumot tárol, melynek típusa - nem találjátok ki - épp az az enum, amelyben az definiálva van. PHP-ben nincs enum, nem része a nyelvnek. Viszont lehet - ha kicsit nehézkesen és nem túl szépen - szimulálni azt.
Mire van szükségünk?
- konstans/statikus elérés
- objektumot kapjunk vissza
- annak típusa egyezzen meg az enum típusával
- adott konstanson keresztül mindig ugyanazt az objektumot kapjuk meg
PHP-ben egy konstans nem tárolhat objektumot, ezért marad a statikus elérés. Statikus blokk szintén nincs PHP-ben, ezért valamikor kézzel triggerelni kell az inicializációt, erre lehetőségünk van az osztály definíció után. Az inicializáció során reflectionnel meg kell keresni az összes publikus static fieldet, példányosítani a megfelelő névvel az enumot és letárolni a fieldben.
Példa
Nagyjából ezt valósítottam meg a precore libben lévő Enum osztályban. Nézzünk rá egy példát. Vegyük a hét napjait.
class Day extends Enum
{
/**
* @var Day
*/
public static $MONDAY;
public static $TUESDAY;
public static $WEDNESDAY;
public static $THURSDAY;
public static $FRIDAY;
public static $SATURDAY;
public static $SUNDAY;
}
Day::init();
Amint az autoloader betölti a fájlt, lefut a Day::init()
statikus metódus, és innentől fogva minden statikus fieldben egy Day típusú objektum lesz. Ha segíteni akarunk az IDE-nek, írjuk mindegyik fölé a típust PHPDoc-ban. Az Enum osztály ad nekünk néhány metódust. Ilyen például a name(), ami visszaadja stringként a field nevét: Day::$MONDAY->name() == "MONDAY"
. Vannak statikus metódusok, mint például a valueOf()
, ami a string alapján visszaadja nekünk az objektumot: Day::valueOf("MONDAY") == Day::$MONDAY
és a values()
, amivel pedig megkapjuk az összes objektumot.
Nem titok, ezek a dolgok Javaból jönnek. Viszont az még tud pár dolgot. Tegyük fel, hogy egy dayOfWeek()
metódust szeretnénk írni, ami visszaadja, hogy az adott nap a hét hanyadik napja. Bővítsük ki az osztályunkat.
class Day extends Enum
{
// ... meglévő kód
public function dayOfWeek()
{
switch ($this) {
case self::$MONDAY: return 1;
case self::$TUESDAY: return 2;
// stb.
}
}
}
A switch nagyon nem szép
Tényleg nem és Javaban ezt sokkal szebben is el lehet intézni, méghozzá konstruktorral. No de a konstruktor nem lehet publikus, mert statikusan érhetjük el csak az objektumokat, vagyis maximum protected lehet. Viszont honnan hívjuk? PHP-ben ez problémás, de készítettem erre is workaroundot. Bővítsük az osztályt egy új függvénnyel és a konstruktorral, vegyük fel az új propertyt és módosítsuk a metódusunkat:
class Day extends Enum
{
// ... statikus fieldek
protected static function constructorArgs()
{
return array(
'MONDAY' => array(1),
'TUESDAY' => array(2),
// stb.
);
}
protected function __construct($dayOfWeek)
{
$this->dayOfWeek = $dayOfWeek;
}
public function dayOfWeek()
{
return $this->dayOfWeek;
}
}
A constructorArgs()
-ban definiált paramétereket sorra meg fogja kapni a konstruktor. Bár jelen esetben nem feltétlenül kapunk rövidebb kódot, azonban
- minden switchet meg lehet így spórolni, amennyiben több metódusban is hasonló logikát szeretnénk beépíteni, ugyanis több paramétert átadhatunk a konstruktornak
- a switch minden metódus hívásnál kiértékelődik, ezzel a módszerrel pedig csak egyszer kell ezt megtenni
- tervezem, hogy mindezt annotációval is el lehessen érni, és akkor elég csak a konstansnál definiálni a konstruktor paramétereit (természetesen ez nem fed le minden esetet)
Valós példa
Készítettem egy TimeUnit enumot, amivel az időegységeket tudjuk könnyen kezelni (igen, ez is Javaból van). A szép az egészben, hogy tetszőleges függvénynek átadhatunk egy TimeUnit objektumot.
function printInMinutes(TimeUnit $timeUnit, $amount)
{
printf("%d %s = %d minutes", $amount, strtolower($timeUnit->name()), $timeUnit->toMinutes($amount));
}
printInMinutes(TimeUnit::$SECONDS, 60);
Na mit fog ez kiírni?
Az Enum-ot és még néhány hasznos osztályt a precore libraryben érhetitek el, illetve composerrel is telepíthetitek. Aki a nagyjából 100 soros implementációra kíváncsi, az itt kutakodjon.