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)
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.
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.
[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.
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?
[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í.
Proc nepouzit constaty trid? Hned to vyhodi naseptavac IDE a nemusi se definovat trida enum..
Komentáře jsou uzavřeny.