"The greatest wealth is to live content with little" --Plato

Enum v PHP

Nov 4, 2008 | koubel | 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.

  • Currently 102.633/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
current rating 3.42/5 (votes: 19)
viewpic 2649x
0 trackbacks - trackback url
5 comments - Add comment
1.   Dundee - Nov 5, 2008 12:04 AM
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 - Nov 5, 2008 5:22 PM
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 - Nov 5, 2008 6:58 PM
[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 - Nov 12, 2008 1:23 PM
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 - Nov 12, 2008 6:29 PM
[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í.
Add comment