datomatic / enum-helper
Simple opinionated framework agnostic PHP 8.1 enum helper
Installs: 123 423
Dependents: 2
Suggesters: 0
Security: 0
Stars: 56
Watchers: 1
Forks: 1
Open Issues: 0
Requires
- php: ^8.1
- ext-ctype: *
- ext-mbstring: *
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.8
- laravel/pint: ^1.18
- pestphp/pest: ^2.0|^3.0
- phpstan/phpstan: ^1.7
README
Enum Helper
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.
Upgrade from v1.x
Please see the upgrade.md file.
Functionalities summary
- 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()
)
Installation
PHP 8.1+ is required.
composer require datomatic/enum-helper
Usage
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.
Jump To
- Invokable Cases
- From
- Enums Inspection
- Enums Equality
- Names
- Values
- Serialization
- Descriptions & Translations
- Properties
Invokable Cases
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());
Examples use static calls to get the primitive value
// 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'
IDE code completion
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 ...
From FromName
This helper adds from()
and tryFrom()
to pure enums,
fromValue()
and tryFromValue()
(alias of from()
and tryFrom()
),
fromName()
and tryFromName()
to all enums
Important Notes:
BackedEnum
instances already implement their ownfrom()
andtryFrom()
methods, which will not be overridden by this trait.
from()
// 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
tryFrom()
// Pure Enum PureEnum::tryFrom('PENDING'); // PureEnum::PENDING PureEnum::tryFrom('MISSING'); // null // BackedEnum StringBackedEnum::tryFrom('P'); // StringBackedEnum::PENDING StringBackedEnum::tryFrom('M'); // null
fromName()
// Pure Enum PureEnum::fromName('PENDING'); // PureEnum::PENDING PureEnum::fromName('MISSING'); // ValueError Exception // BackedEnum StringBackedEnum::fromName('PENDING'); // StringBackedEnum::PENDING StringBackedEnum::fromName('MISSING'); // ValueError Exception
tryFromName()
// Pure Enum PureEnum::tryFromName('PENDING'); // PureEnum::PENDING PureEnum::tryFromName('MISSING'); // null // BackedEnum StringBackedEnum::tryFromName('PENDING'); // StringBackedEnum::PENDING StringBackedEnum::tryFromName('MISSING'); // null
Inspection
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()
).
isPure()
, isBacked()
, isIntBacked()
and isStringBacked()
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()
and doesntHave()
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()
and doesntHaveName()
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()
and doesntHaveValue()
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
Equality
This helper permits to compare an enum instance (is()
,isNot()
) and search if it is present inside an array (in()
,notIn()
).
is()
and isNot()
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()
and notIn()
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
Names
This helper offer names()
and namesByValue()
methods.
names()
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']
namesByValue()
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']
Values
This helper offer values()
and valuesByName()
methods.
values()
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]
valuesByName()
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]
Serialization
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; ...
serialize()
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
unserialize()
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
Global unserializeEnum() helper
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); }
json_encode on PureEnum
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"'
Descriptions and Translations
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'
Localization
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'
descriptions()
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']
descriptionsByValue()
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']
nullableDescriptionsByValue()
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',...
descriptionsByName()
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']
Labels
The EnumLabel
trait it's the same of EnumDescription
but you can use if prefer call label
method instead description
.
Properties
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');