Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

4. 11. 2008 - Komentáře (6) PHP

Enum v PHP

Jedna z věcí, která v PHP chybí je podpora výčtového typu - enum. Je to vlastně množina konstant - enumerátorů, každá má svůj identifikátor. Proměnná takového výčtového typu pak nabývá jednu z konstant. Bez výčtového typu se dá celkem pohodlně obejít, ale má i své výhody, zejména tu, že kód s jeho použitím je přehlednější a tak nějak dokumentovaný samo sebou. Poprvé se s ním asi většina setkala v Pascalu, podporuje ho C/C++, Java, C# a další. V PHP není, nicméně jeho vlastnosti lze napodobit i v userland řešení. Našel jsem jeden poměrně zajímavý způsob, který používá i nové vlastnosti PHP 5.3. Pojďme se na něj podívat.

Od výčtového typu budeme požadovat výše uvedené chování, v PHP by se nám hodil i type hinting. Vše se bude demonstrovat na typu, který bude představovat množinu typů DNS záznamů (A, PTR, CNAME, MX, …). Kvůli type hintingu začneme nějakou základní třídou.

abstract class Enum {
	final public function __toString() {
		return get_class($this);
	}
}

Naše třída pro typy DNS záznamů pak bude jejím potomkem a enumerátory pak jejími potomky.

abstract class DNSRecordType extends Enum {}
 
class A extends DNSRecordType {}
class CNAME extends DNSRecordType {}
class MX extends DNSRecordType {}
 
function printDnsRecord(DNSRecordType $type, ?) {
       // We can now be sure $type is a DNSRecordType
}

Jedním z problémů, které toto řešení přináší, je nemožnost operace porovnání na proměnných takového typu.

(new CNAME) !== (new CNAME)

To můžeme obejít tak, že použijeme factory metodu na vytváření instancí daného typu a použité enumerátory pak budeme uchovávat v poli. Zároveň zde můžeme použít syntaktické novinky v PHP 5.3 - __callStatic. S tím polem hodnot a statickou factory metodou bych řekl, že jsem to snad dokonce někde viděl jako design pattern nebo tak něco.

abstract class Enum {
	protected static $instances = array();
 
	final private function __construct() {}
 
	final public function __toString() {
		return get_class($this);
	}
 
	final public static function get($name) {
		if(is_subclass_of($name, "Enum")) {
			if(array_key_exists($name, self::$instances)) {
				return self::$instances[$name];
			} else {
				return self::$instances[$name] = new $name();
			}
		} else {
			throw Exception();
		}
	}
 
	final public static function __callStatic($name, $args) {
		return self::get($name);
	}
}

Pro PHP 5.3 je možné vytvářet instance výčtového typu např. přes

DNSRecordType::CNAME()

Pro PHP 5.2 pak musíme volat přímo factory metodu

DNSRecordType::get('CNAME')

Další vlastnosti, které autor implementoval je chování výčtového typu jako v jazyce C. Pokud se nic neuvede, tak interně budou enumerátory představovat integer hodnoty, přičemž uživatelsky je možné je předefinovat a dokonce to, že jen určité enumerátory mohou mít uživatelsky definovanou hodnotu, zbylé pak budou mít hodnotu předešlého enumerátoru zvětšenou o 1.

Jako bonus autor implementoval i podporu Iterátoru pro enumerátory, celkově dost zajímavé. Výpisy finální třídy jsou na autorově blogu, má tam i git repositář se zdrojovými kódy.


Komentáře (6)

  1. Dundee - 5. 11. 2008 00:04

    Tak na to bych asi jen tak sám nepřišel :)

    CallStatic vypadá jako moc pěkná věc, už aby tu 5.3 byla. Hlavně toho late static bindingu se nemůžu dočkat.

  2. v6ak - 5. 11. 2008 17:22

    DNSRecordType::get(CNAME)? Spíš DNSRecordType::get('CNAME'), ne?
    Jinak bych měl jednu výhradu: myslím, že třídy jako A, CNAME apod. by měly dostat prefix (nebo ns, i když to je sporné): DNSRecordType_A, DNSRecordType_CNAME apod.

  3. koubel - 5. 11. 2008 18:58

    [2] opraveno, jak jsem to tam pastoval, tak se mě to nějak rozbilo.

    Prefix/namespace by se asi hodil, pak by se to muselo drobně upravit.

  4. xmsi - 12. 11. 2008 13:23

    volne cituji: (a) bez vyctoveho typu lze se obejit, (b) enum dela kod prehlednejsi...

    tak tedy nevim, zda trida Enum a jeji potomci splnuji (b), rekl bych ze ne a smele se drzel (a), jiny nazor?

  5. koubel - 12. 11. 2008 18:29

    [4] Ti, kdo mají rádi chování enum typu jako v C/C++, Pascalu a často používají type hinting, tak to pravděpodně přivítají, ostatní to asi moc nevyužijí.

  6. Radek - 27. 7. 2011 11:12

    Proc nepouzit constaty trid? Hned to vyhodi naseptavac IDE a nemusi se definovat trida enum..

Komentáře jsou uzavřeny.