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.
- Invokable cases: get the value of enum "invoking" it statically
- Construct enum by name or value:
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()
) - Unique ID: get an unique identifier from instance or instance from identifier (
uniqueId()
,fromUniqueId()
) - Descriptions & Translations: add description to enum with optional translation (
description()
,descriptions()
,descriptionsByName()
,descriptionsByValue()
,nullableDescriptionsByValue()
)
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 EnumUniqueId
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
- Froms
- Enums Inspection
- Enums Equality
- Names
- Values
- Unique ID
- Descriptions & Translations
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 EnumUniqueId
.
use Datomatic\EnumHelper\Traits\EnumUniqueId;
enum PureEnum
{
use EnumUniqueId;
...
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->uniqueId(); // Namespace\PureEnum.PENDING
$enum = StringBackedEnum::NO_RESPONSE;
$enum->uniqueId(); // Namespace\StringBackedEnum.NO_RESPONSE
This method returns an enum instance from unique identifier.
PureEnum::fromUniqueId('Namespace\PureEnum.PENDING'); // PureEnum::PENDING
IntBackedEnum::fromUniqueId('Namespace\IntBackedEnum.PENDING'); // IntBackedEnum::PENDING
IntBackedEnum::fromUniqueId('NOT.valid.uniqueId'); // throw InvalidUniqueId Exception
IntBackedEnum::fromUniqueId('Wrong\Namespace\IntBackedEnum.PENDING'); // throw InvalidUniqueId Exception
IntBackedEnum::fromUniqueId('Namespace\IntBackedEnum.MISSING'); // throw InvalidUniqueId Exception
The method fromUniqueId()
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 uniqueId like this:
use Datomatic\EnumHelper\Exceptions\InvalidUniqueId;
public function getEnumFromUniqueId(string $uniqueId): object
{
if (
!strpos($uniqueId, '.')
|| substr_count($uniqueId, '.') !== 1
) {
throw InvalidUniqueId::uniqueIdFormatIsInvalid($uniqueId);
}
list($enumClass, $enumName) = explode('.', $uniqueId);
foreach ($enumClass::cases() as $case){
if( $case->name === $enumName){
return $case;
}
}
}
throw InvalidUniqueId::caseNotPresent($case);
}
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 EnumUniqueId trait
return translate($this->uniqueId(), $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',...
// 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']
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',...