Enums for PHP 5.4+
Installation
Add package to composer.json., (*1)
{
"require": {
"timetoogo/enums": "*"
}
}
Or manually require {Path to Enum}/Loader.php, (*2)
Summary
- An enum is a group of possible values:
DayOfWeek
- An an instance of an enum represents an underlying value:
DayOfWeek::Thursday()->GetValue()
- Two enum instances of the same type, representing the same value are identical:
DayOfWeek::Thursday() === DayOfWeek::Thursday()
- Enums are type safe:
function SetDay(DayOfWeek $DayOfWeek)
- Enums are extensible:
DayOfWeek::Thursday()->GetTomorrow()
- Enum values are serializable:
DayOfWeek::Serialize(DayOfWeek::Thursday())
- Enum values can be represented as a string:
(string)DayOfWeek::Thursday()
An introduction
final class Boolean extends \Enum\Simple {
public static function True() {
return self::Representing('True');
}
public static function False() {
return self::Representing('False');
}
}
Congrats, we have successfully reimplemented the boolean using enums., (*3)
Lets test it out:, (*4)
var_export(Boolean::True() === Boolean::True()); //true
var_export(Boolean::True() === Boolean::False()); //false
var_export(Boolean::False() === Boolean::False()); //true
Great it works,
now lets reimplement ! operator., (*5)
final class Boolean extends \Enum\Simple {
public static function True() {
return self::Representing('True');
}
public static function False() {
return self::Representing('False');
}
public function Not() {
return $this === self::True() ? self::False() : self::True();
}
}
And test it out:, (*6)
Boolean::True()->Not() === Boolean::True(); //false
Boolean::True() === Boolean::False()->Not(); //true
Boolean::False()->Not()->Not() === Boolean::False(); //true
Cool, but pointless., (*7)
Lets do something useful and create the days of the week (as in summary) :, (*8)
final class DayOfWeek extends Enum\Simple {
public static function Monday() { return self::Representing('Monday'); }
public static function Tuesday() { return self::Representing('Tuesday'); }
public static function Wednesday() { return self::Representing('Wednesday'); }
public static function Thursday() { return self::Representing('Thursday'); }
public static function Friday() { return self::Representing('Friday'); }
public static function Saturday() { return self::Representing('Saturday'); }
public static function Sunday() { return self::Representing('Sunday'); }
public function GetTomorrow() {
if($this === self::Sunday()) {
return self::Monday();
}
else {
$All = self::All();
return $All[array_search($this, $All) + 1];
}
}
}
Notice something? The enum values are simply represented by their method name., (*9)
Well, lets keep DRY and see how we go:, (*10)
final class DayOfWeek extends Enum\Simple {
public static function Monday() { return self::Representing(__FUNCTION__); }
public static function Tuesday() { return self::Representing(__FUNCTION__); }
public static function Wednesday() { return self::Representing(__FUNCTION__); }
public static function Thursday() { return self::Representing(__FUNCTION__); }
public static function Friday() { return self::Representing(__FUNCTION__); }
public static function Saturday() { return self::Representing(__FUNCTION__); }
public static function Sunday() { return self::Representing(__FUNCTION__); }
public function GetTomorrow() {
if($this === self::Sunday()) {
return self::Monday();
}
else {
$All = self::All();
return $All[array_search($this, $All) + 1];
}
}
}
Well that didn't do much, and it is still extremely verbose! And ugly., (*11)
Relax, there is a solution:, (*12)
, (*13)
final class DayOfWeek extends \Enum\Simple {
use \Enum\Values {
_ as Monday;
_ as Tuesday;
_ as Wednesday;
_ as Thursday;
_ as Friday;
_ as Saturday;
_ as Sunday;
}
public function GetTomorrow() {
if($this === self::Sunday()) {
return self::Monday();
}
else {
$All = self::All();
return $All[array_search($this, $All) + 1];
}
}
}
That's better! Note that there will be no difference in functionality between the above three examples., (*14)
Note that using the \Enum\Values only works for PHP >5.4.16, (*15)
If your enums values are represented by the method names, you can utilise the \Enum\Values trait. This trait contains a single static method _. You can alias this method to the required enum values, and the aliased methods will return the enum representing the method name string., (*16)
Using this trait the equivalent Boolean would become:, (*17)
final class Boolean extends \Enum\Simple {
use \Enum\Values {
_ as True;
_ as False;
}
public function Not() {
return $this === self::True() ? self::False() : self::True();
}
}
Conversion To String
Override the protected string ToString(mixed $Value) to customize the conversion to string:, (*18)
final class DayOfWeek extends \Enum\Simple {
use \Enum\Values {
_ as Monday;
_ as Tuesday;
_ as Wednesday;
_ as Thursday;
_ as Friday;
_ as Saturday;
_ as Sunday;
}
protected function ToString($Value) {
return 'Today could be ' . strtolower($Value) . '.';
}
}
echo DayOfWeek::Saturday(); //Today could be saturday.
Serialization
Enums values can also be fully serialized/unserialized using the Enum\Base::Serialize(Enum\Base $Enum) and Enum\Base::Unserialize(string $SerializedEnum) repectively. This will work for any defined enum., (*19)
If you want to un/serialize an enum of a specific type, you can can call either method in the context of the required enum type:, (*20)
$Monday = DayOfWeek::Monday();
$SerializedMonday = Enum\Base::Serialize($Monday);//Ok
$SerializedMonday = DayOfWeek::Serialize($Monday);//Ok
$SerializedMonday = MonthOfYear::Serialize($Monday);//ERROR!
$Monday = Enum\Base::Unserialize($SerializedMonday); //Ok
$Monday = DayOfWeek::Unserialize($SerializedMonday); //Ok
$Monday = MonthOfYear::Unserialize($SerializedMonday); //ERROR!
Comparison and Equality
An two enum instances of the same type, representing the same value must be equal. Comparison results using === operator can be seen below., (*21)
$Monday = DayOfWeek::Monday();
$Tuesday = DayOfWeek::Tuesday();
$Monday === DayOfWeek::Monday(); //true
$Monday === DayOfWeek::FromValue(DayOfWeek::Monday()->GetValue()); //true
$Monday === DayOfWeek::Unserialize(DayOfWeek::Serialize(DayOfWeek::Monday())); //true
$Monday === $Tuesday; //false
As you can see, within a enum there is only ever one instance representing any given value., (*22)
Clean comprehension API
-
Enum\Base::Map(callable $MappingCallback) Maps the enum instances with the supplied callback
-
Enum\Base::MapValues(callable $MappingCallback) Maps the represented values with the supplied callback
-
Enum\Base::Filter(callable $MappingCallback) Filters the enum instances with the supplied callback
-
Enum\Base::FilterByValue(callable $MappingCallback) Filters the enums by their represented value with the supplied callback
-
Enum\Base::FirstOrDefault(callable $MappingCallback) Returns the first enum according to the filter callback or the default value if none is matched
-
FirstOrDefaultByValue(callable $MappingCallback)
Usage
final class DayOfWeek extends \Enum\Simple {
use \Enum\Values {
_ as Monday;
_ as Tuesday;
_ as Wednesday;
_ as Thursday;
_ as Friday;
_ as Saturday;
_ as Sunday;
}
public static function IsWeekEnd(self $DayOfWeek) {
return $DayOfWeek === self::Saturday() || $DayOfWeek === self::Sunday();
}
public static function GetWeekDays() {
return self::Filter(function (self $DayOfWeek) {
return !self::IsWeekEnd($DayOfWeek);
});
}
public static function GetWeekEndDays() {
return self::Filter([__CLASS__, 'IsWeekEnd']);
}
}
A more sophisticated example
If your requirements are more complex, enums can represent any serializable value:, (*23)
class Country extends Enum\Simple {
public static function USA() {
return self::Representing(
[
'Name' => 'United States of America',
'Population' => 317500000,
'Area' => 9826675
]);
}
public static function Australia() {
return self::Representing(
[
'Name' => 'Australia',
'Population' => 23351119,
'Area' => 7692024
]);
}
public static function SouthAfrica() {
return self::Representing(
[
'Name' => 'South Africa',
'Population' => 52981991,
'Area' => 1221037
]);
}
public static function FromName($Name) {
return self::FirstOrDefaultByValue(
function ($Value) use ($Name) {
return $Value['Name'] === $Name;
});
}
protected function ToString($Value) {
return $Value['Name'];
}
public function PopulationDensity() {
$Country = $this->GetValue();
return $Country['Population'] / $Country['Area'];
}
}
We have defined an enum representing some countries and have provided methods to determine information on that data., (*24)
Usage
$SouthAfrica = Country::SouthAfrica();
echo sprintf('%s has a population density of: %s/Km²',
$SouthAfrica,
$SouthAfrica->PopulationDensity());