A simple and opinionated collections of PHP 8.1 enum helpers inspired by archtechx/enums and BenSampo/laravel-enum.
This package is framework agnostic, but if you use Laravel consider to use this linked package datomatic/laravel-enum-helper and datomatic/laravel-enum-collections.
Please see the upgrade.md file.
- Invokable cases: get the value of enum "invoking" it statically
- Construct enum by name or value:
wrap()
,from()
,tryFrom()
,fromName()
,tryFromName()
,fromValue()
,tryFromValue()
methods - Enums Inspection:
isPure()
,isBacked()
,has()
,hasName()
,hasValue()
methods - Enums Equality:
is()
,isNot()
,in()
,notIn()
methods - Names: methods to have a list of case names (
names()
,namesByValue()
) - Values: methods to have a list of case values (
values()
,valuesByName()
) - Serialization: get an unique identifier from instance or instance from identifier (
serialize()
,unserialize()
) - Descriptions & Translations: add description to enum with optional translation (
description()
,descriptions()
,descriptionsByName()
,descriptionsByValue()
,nullableDescriptionsByValue()
) - Labels: add label to enum (
label()
,labels()
,labelsByName()
,labelsByValue()
,nullableLabelsByValue()
) - Properties: methods to have a list of properties (
dynamicList()
,dynamicByKey()
)
PHP 8.1+ is required.
composer require datomatic/enum-helper
You can use the traits you need, but for convenience, you can use only the EnumHelper
trait that includes (EnumInvokable
, EnumFroms
, EnumNames
, EnumValues
, EnumInspection
, EnumEquality
).
EnumDescription
and EnumSerialization
are separated from EnumHelper
because they cover edge cases.
The helper support both pure enum (e.g. PureEnum
, PascalCasePureEnum
) and BackedEnum
(e.g. IntBackedEnum
, StringBackedEnum
).
In all examples we'll use the classes described below:
use Datomatic\EnumHelper\EnumHelper;
// Pure enum
enum PureEnum
{
use EnumHelper;
case PENDING;
case ACCEPTED;
case DISCARDED;
case NO_RESPONSE;
}
enum PascalCasePureEnum
{
use EnumHelper;
case Pending;
case Accepted;
case Discarded;
case NoResponse;
}
// BackedEnum
enum StringBackedEnum: string
{
use EnumHelper;
case PENDING = 'P';
case ACCEPTED = 'A';
case DISCARDED = 'D';
case NO_RESPONSE = 'N';
}
enum IntBackedEnum: int
{
use EnumHelper;
case PENDING = 0;
case ACCEPTED = 1;
case DISCARDED = 2;
case NO_RESPONSE = 3;
}
The package works with cases written in UPPER_CASE, snake_case and PascalCase.
- Invokable Cases
- From
- Enums Inspection
- Enums Equality
- Names
- Values
- Serialization
- Descriptions & Translations
- Properties
This helper lets you get the value of a BackedEnum
, or the name of a pure enum, by "invoking" it both statically (PureEnum::pending()
), and as an instance ($status()
).
A good approach is to call methods in camelCase mode, but you can invoke the enum in all cases ::STATICALLY()
, ::statically()
or ::Statically()
.
IntBackedEnum::PENDING // PureEnum enum instance
IntBackedEnum::pending(); // 0
That way permits you to use enum invoke into an array keys definition:
'statuses' => [
PureEnum::pending() => 'some configuration',
...
or in database interactions $db_field_definition->default(PureEnum::pending())
or invoke instances to get the primitive value
public function updateStatus(int $status): void;
$task->updateStatus(IntBackedEnum::pending());
// Pure Enum
PureEnum::noResponse(); // 'NO_RESPONSE'
PureEnum::NO_RESPONSE(); // 'NO_RESPONSE'
PureEnum::NoResponse(); // 'NO_RESPONSE'
// Pure Enum with PascalCase
PascalCasePureEnum::noResponse(); // 'NoResponse'
PascalCasePureEnum::NO_RESPONSE(); // 'NoResponse'
PascalCasePureEnum::NoResponse(); // 'NoResponse'
// IntBackedEnum
IntBackedEnum::pending(); // 0
// StringBackedEnum
StringBackedEnum::pending(); // 'P'
To have a code completion you can get autosuggestions while typing the enum case and then add () or you can add phpDoc @method tags to the enum class to define all invokable cases like this:
/**
* @method static string pending()
* @method static string accepted()
* @method static string discarded()
* @method static string noResponse()
*/
enum PureEnum
...
This helper adds from()
and tryFrom()
to pure enums,
fromValue()
and tryFromValue()
(alias of from()
and tryFrom()
),
fromName()
and tryFromName()
to all enums
BackedEnum
instances already implement their ownfrom()
andtryFrom()
methods, which will not be overridden by this trait.
// Pure Enum
PureEnum::from('PENDING'); // PureEnum::PENDING
PascalCasePureEnum::from('Pending'); // PascalCasePureEnum::Pending
PureEnum::from('MISSING'); // ValueError Exception
// BackedEnum
StringBackedEnum::from('P'); // StringBackedEnum::PENDING
StringBackedEnum::from('M'); // ValueError Exception
// Pure Enum
PureEnum::tryFrom('PENDING'); // PureEnum::PENDING
PureEnum::tryFrom('MISSING'); // null
// BackedEnum
StringBackedEnum::tryFrom('P'); // StringBackedEnum::PENDING
StringBackedEnum::tryFrom('M'); // null
// Pure Enum
PureEnum::fromName('PENDING'); // PureEnum::PENDING
PureEnum::fromName('MISSING'); // ValueError Exception
// BackedEnum
StringBackedEnum::fromName('PENDING'); // StringBackedEnum::PENDING
StringBackedEnum::fromName('MISSING'); // ValueError Exception
// Pure Enum
PureEnum::tryFromName('PENDING'); // PureEnum::PENDING
PureEnum::tryFromName('MISSING'); // null
// BackedEnum
StringBackedEnum::tryFromName('PENDING'); // StringBackedEnum::PENDING
StringBackedEnum::tryFromName('MISSING'); // null
This helper permits check the type of enum (isPure()
,isBacked()
) and if enum contains a case name or value (has()
, doesntHave()
, hasName()
, doesntHaveName()
, hasValue()
, doesntHaveValue()
).
With these methods you can check the type of the enum instance.
PureEnum::PENDING->isPure() // true
PureEnum::PENDING->isBacked() // false
IntBackedEnum::PENDING->isPure() // false
IntBackedEnum::PENDING->isIntBacked() // true
StringBackedEnum::PENDING->isBacked() // true
StringBackedEnum::PENDING->isStringBacked() // true
has()
method permit checking if an enum has a case (name or value) by passing int, string or enum instance.
For convenience, there is also an doesntHave()
method which is the exact reverse of the has()
method.
PureEnum::has('PENDING') // true
IntBackedEnum::has(10) // false
IntBackedEnum::has(1) // true
IntBackedEnum::has('1') // true
StringBackedEnum::has('ACCEPTED') // true
StringBackedEnum::has('A') // true
StringBackedEnum::doesntHave('A') // false
hasName()
method permit checking if an enum has a case name.
For convenience, there is also an doesntHaveName()
method which is the exact reverse of the hasName()
method.
PureEnum::hasName('PENDING') // true
PureEnum::hasName('P') // false
IntBackedEnum::hasName('ACCEPTED') // true
IntBackedEnum::hasName(1) // false
StringBackedEnum::doesntHaveName('ACDSIED') // true
StringBackedEnum::hasName('A') // false
hasValue()
method permit checking if an enum has a case by passing int, string or enum instance.
For convenience, there is also an doesntHaveValue()
method which is the exact reverse of the hasValue()
method.
PureEnum::hasValue('PENDING') // true
PureEnum::hasValue('P') // false
IntBackedEnum::hasValue('ACCEPTED') // false
IntBackedEnum::hasValue(1) // true
StringBackedEnum::doesntHaveValue('Z') // true
StringBackedEnum::hasValue('A') // true
This helper permits to compare an enum instance (is()
,isNot()
) and search if it is present inside an array (in()
,notIn()
).
is()
method permit checking the equality of an instance against an enum instance, a case name, or a case value.
For convenience, there is also an isNot()
method which is the exact reverse of the is()
method.
$enum = PureEnum::PENDING;
$enum->is(PureEnum::PENDING); // true
PureEnum::PENDING->is(PureEnum::ACCEPTED); // false
PureEnum::PENDING->is('PENDING'); // true
PureEnum::PENDING->is('ACCEPTED'); // false
PureEnum::PENDING->isNot('ACCEPTED'); // true
$backedEnum = IntBackedEnum::PENDING;
$backedEnum->is(IntBackedEnum::PENDING); // true
IntBackedEnum::PENDING->is(IntBackedEnum::ACCEPTED); // false
IntBackedEnum::PENDING->is(0); // true
IntBackedEnum::PENDING->is('PENDING'); // true
StringBackedEnum::PENDING->is('P'); // true
StringBackedEnum::PENDING->isNot('P'); // false
in()
method permit to see if an instance matches on an array of instances, names or values.
For convenience, there is also a notIn()
method which is the exact reverse of the i()
method.
$enum = PureEnum::PENDING;
$enum->in([PureEnum::PENDING,PureEnum::ACCEPTED]); // true
PureEnum::PENDING->in([PureEnum::DISCARDED, PureEnum::ACCEPTED]); // false
PureEnum::PENDING->in(['PENDING', 'ACCEPTED']); // true
PureEnum::PENDING->in(['ACCEPTED', 'DISCARDED']); // false
PureEnum::PENDING->notIn(['ACCEPTED']); // true
$backedEnum = IntBackedEnum::PENDING;
$backedEnum->in([IntBackedEnum::PENDING, IntBackedEnum::ACCEPTED]); // true
IntBackedEnum::PENDING->in([IntBackedEnum::ACCEPTED])// false
IntBackedEnum::PENDING->in([0, 1, 2]); // true
IntBackedEnum::PENDING->in([2, 3]); // false
IntBackedEnum::PENDING->in(['PENDING', 'ACCEPTED']); // true
IntBackedEnum::PENDING->in(['DISCARDED', 'ACCEPTED']); // false
StringBackedEnum::PENDING->in(['P', 'D']); // true
StringBackedEnum::PENDING->notIn(['A','D']); // true
This helper offer names()
and namesByValue()
methods.
This method returns a list of case names in the enum.
PureEnum::names(); // ['PENDING', 'ACCEPTED', 'DISCARDED', 'NO_RESPONSE']
PascalCasePureEnum::names(); // ['Pending', 'Accepted', 'Discarded', 'NoResponse']
StringBackedEnum::names(); // ['PENDING', 'ACCEPTED', 'DISCARDED', 'NO_RESPONSE']
// Subset
PureEnum::names([PureEnum::NO_RESPONSE, PureEnum::DISCARDED]); // ['NO_RESPONSE', 'DISCARDED']
PascalCasePureEnum::names([PascalCasePureEnum::Accepted, PascalCasePureEnum::Discarded]); // ['Accepted', 'Discarded']
This method returns an associative array of [value => name] on BackedEnum
, [name => name] on pure enum.
PureEnum::namesByValue(); // [ 'PENDING' => 'PENDING', 'ACCEPTED' => 'ACCEPTED', 'DISCARDED' => 'DISCARDED'...
StringBackedEnum::namesByValue(); // [ 'P' => 'PENDING', 'A' => 'ACCEPTED', 'D' => 'DISCARDED'...
IntBackedEnum::namesByValue(); // [ 0=>'PENDING', 1=>'ACCEPTED', 2=>'DISCARDED'...
// Subset
IntBackedEnum::namesByValue([IntBackedEnum::NO_RESPONSE, IntBackedEnum::DISCARDED]); // [ 3=>'NO_RESPONSE', 2=>'DISCARDED']
This helper offer values()
and valuesByName()
methods.
This method returns a list of case values for BackedEnum
or a list of case names for pure enums.
PureEnum::values(); // ['PENDING', 'ACCEPTED', 'DISCARDED', 'NO_RESPONSE']
StringBackedEnum::values(); // ['P', 'A', 'D', 'N']
IntBackedEnum::values(); // [0, 1, 2, 3]
// Subset
PureEnum::values([PureEnum::NO_RESPONSE, PureEnum::DISCARDED]); // ['NO_RESPONSE', 'DISCARDED']
StringBackedEnum::values([StringBackedEnum::NO_RESPONSE, StringBackedEnum::DISCARDED]); // ['N', 'D']
IntBackedEnum::values([IntBackedEnum::NO_RESPONSE, IntBackedEnum::DISCARDED]); // [3, 2]
This method returns a associative array of [name => value] on BackedEnum
, [name => name] on pure enum.
PureEnum::valuesByName(); // ['PENDING' => 'PENDING','ACCEPTED' => 'ACCEPTED','DISCARDED' => 'DISCARDED',...]
StringBackedEnum::valuesByName(); // ['PENDING' => 'P','ACCEPTED' => 'A','DISCARDED' => 'D','NO_RESPONSE' => 'N']
IntBackedEnum::valuesByName(); // ['PENDING' => 0,'ACCEPTED' => 1,'DISCARDED' => 2,'NO_RESPONSE' => 3]
// Subset
PureEnum::valuesByName([PureEnum::NO_RESPONSE, PureEnum::DISCARDED]); // ['NO_RESPONSE' => 'NO_RESPONSE', 'DISCARDED' => 'DISCARDED']
StringBackedEnum::valuesByName([StringBackedEnum::NO_RESPONSE, StringBackedEnum::DISCARDED]); // ['NO_RESPONSE' => 'N', 'DISCARDED' => 'D']
IntBackedEnum::valuesByName([IntBackedEnum::NO_RESPONSE, IntBackedEnum::DISCARDED]); // ['NO_RESPONSE' => 3, 'DISCARDED' => 2]
This helper permits to get an unique identifier from enum or an enum instance from identifier.
The helper is not included on the base EnumHelper
trait and does not depend on it, so if you need it you must use EnumSerialization
.
use Datomatic\EnumHelper\Traits\EnumSerialization;
enum PureEnum
{
use EnumSerialization;
...
This method returns the enum unique identifier based on Namespace\ClassName::CASE_NAME. You can use this identifier to save multiple types of enums in a database on a polymorphic column.
PureEnum::PENDING->serialize(); // Namespace\PureEnum.PENDING
$enum = StringBackedEnum::NO_RESPONSE;
$enum->serialize(); // Namespace\StringBackedEnum.NO_RESPONSE
This method returns an enum instance from unique identifier.
PureEnum::unserialize('Namespace\PureEnum::PENDING'); // PureEnum::PENDING
IntBackedEnum::unserialize('Namespace\IntBackedEnum::PENDING'); // IntBackedEnum::PENDING
IntBackedEnum::unserialize('NOT::valid::uniqueId'); // throw InvalidSerializedValue Exception
IntBackedEnum::unserialize('Wrong\Namespace\IntBackedEnum::PENDING'); // throw InvalidSerializedValue Exception
IntBackedEnum::unserialize('Namespace\IntBackedEnum::MISSING'); // throw InvalidSerializedValue Exception
The method unserialize()
has little possibility of use because it's related to only an enum class.
A better approach is to create a global helper to instantiate any enum from serialization like this:
use Datomatic\EnumHelper\Exceptions\InvalidSerializedValue;
public function unserializeEnum(string $value): object
{
if (
!strpos($value, '.')
|| substr_count($value, '.') !== 1
) {
throw InvalidSerializedValue::serializedFormatIsInvalid($value);
}
list($enumClass, $enumName) = explode('.', $value);
foreach ($enumClass::cases() as $case){
if( $case->name === $enumName){
return $case;
}
}
}
throw InvalidSerializedValue::caseNotPresent($case);
}
By default, you can't use json_encode()
on PureEnum
because it hasn't a value.
Using this trait and implementing JsonSerializable
interface you can use json_encode()
.
enum Status implements \JsonSerializable
{
use EnumSerialization;
case PENDING;
case ACCEPTED;
}
json_encode(Status::PENDING); // '"PENDING"'
This helper permits to have a description of each case of an enum. Work with both singular language and multilingual application. This is useful when you need descriptions to characterize the cases better or in a multilingual context.
The helper is not included on the base EnumHelper
trait and does not depend on it, so if you need it you must use EnumDescription
and implement the abstract description()
method to define the descriptions.
You can use it on both pure enums and BackedEnum
.
use Datomatic\EnumHelper\EnumHelper;
use Datomatic\EnumHelper\Traits\EnumDescription;
enum StringBackedEnum: string
{
use EnumHelper;
use EnumDescription;
case PENDING = 'P';
case ACCEPTED = 'A';
case DISCARDED = 'D';
case NO_RESPONSE = 'N';
public function description(?string $lang = null): string
{
return match ($this) {
self::PENDING => 'Await decision',
self::ACCEPTED => 'Recognized valid',
self::DISCARDED => 'No longer useful',
self::NO_RESPONSE => 'No response',
};
}
After the implementation of description()
method you can use it
PureEnum::PENDING->description(); // 'Await decision'
You can change the description()
method with your translation method/helper to translate the descriptions.
public function description(?string $lang = null): string
{
// this is only an example of implementation... translate method not exist
// if $lang is null you have to use the current locale
return return translate('status.'$this->name, $lang);
// or translate each case
return match ($this) {
self::PENDING => translate('Await decision'),
self::ACCEPTED => translate('Recognized valid'),
self::DISCARDED => translate('No longer useful'),
self::NO_RESPONSE => translate('No response'),
};
//or use EnumSerialization trait
return translate($this->serialize(), $lang);
}
After the implementation of description
method you can use it
$enum = PureEnum::PENDING;
$enum->description(); // 'Await decision'
$enum->description('it'); // ๐ฎ๐น 'In attesa'
This method returns a list of case descriptions of enum.
StringBackedEnum::descriptions(); // ['Await decision','Recognized valid','No longer useful','No response']
// Subset
StringBackedEnum::descriptions([StringBackedEnum::ACCEPTED, StringBackedEnum::NO_RESPONSE]); // ['Recognized valid','No response']
This method returns an associative array of [value => description] on BackedEnum
, [name => description] on pure enum.
StringBackedEnum::descriptionsByValue(); // ['P' => 'Await decision', 'A' => 'Recognized valid',...
PureEnum::descriptionsByValue(); // ['PENDING' => 'Await decision', 'ACCEPTED' => 'Recognized valid',...
PureEnum::descriptionsByValue(lang: 'it'); // ['PENDING' => 'In attesa', 'ACCEPTED' => 'Valido',...
// Subset
StringBackedEnum::descriptionsByValue([StringBackedEnum::DISCARDED, StringBackedEnum::ACCEPTED]); // ['D' => 'No longer useful', 'A' => 'Recognized valid']
PureEnum::descriptionsByValue([[PureEnum::PENDING, PureEnum::DISCARDED]); // ['PENDING' => 'Await decision', 'DISCARDED' => 'No longer useful']
PureEnum::descriptionsByValue([[PureEnum::PENDING, PureEnum::DISCARDED],'it'); // ['PENDING' => 'In attesa', 'DISCARDED' => 'Scartato']
This method prepend to descriptionsByValue()
returns a default value usefull when do you need nullable select on a form.
StringBackedEnum::nullableDescriptionsByValue('Select value'); // [null => 'Select value', 'P' => 'Await decision', 'A' => 'Recognized valid',...
This method returns an associative array of [name => description].
StringBackedEnum::descriptionsByName(); // ['PENDING' => 'Await decision', 'ACCEPTED' => 'Recognized valid',...
PureEnum::descriptionsByName(lang: 'it'); // ['PENDING' => 'In attesa', 'ACCEPTED' => 'Valido',...
// Subset
StringBackedEnum::descriptionsByName([StringBackedEnum::DISCARDED, StringBackedEnum::ACCEPTED]); // ['DISCARDED' => 'No longer useful', 'ACCEPTED' => 'Recognized valid']
PureEnum::descriptionsByName([[PureEnum::PENDING, PureEnum::DISCARDED],'it'); // ['PENDING' => 'In attesa', 'DISCARDED' => 'Scartato']
The EnumLabel
trait it's the same of EnumDescription
but you can use if prefer call label
method instead description
.
The EnumProperties
trait it's used to get properties list dynamically.
If your enum has a method to define a property like color()
you can use this trait in this mode:
StringBackedEnum::dynamicList(method: 'color');
StringBackedEnum::dynamicByKey(key: 'value', method: 'color');
//Subset and Locale
StringBackedEnum::dynamicList(method: 'color',[StringBackedEnum::DISCARDED, StringBackedEnum::ACCEPTED], 'it');
StringBackedEnum::dynamicByKey(key: 'value', method: 'color', [StringBackedEnum::DISCARDED, StringBackedEnum::ACCEPTED], 'it');