szjani.hu

A régi blogom, ahova lehet írok majd még...

Enum PHP-ben?

2014-01-28 3 perc olvasási idő

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.

comments powered by Disqus