Helpers for making PHP enums more lovable.

Related tags

Miscellaneous enums
Overview

Enums

A collection of enum helpers for PHP.

You can read more about the idea on Twitter. I originally wanted to include the InvokableCases helper in archtechx/helpers, but it makes more sense to make it a separate dependency and use it inside the other package.

Installation

PHP 8.1+ is required.

composer require archtechx/enums

Usage

InvokableCases

This helper lets you get the value of a backed enum, or the name of a pure enum, by "invoking" it — either statically (MyEnum::FOO() instead of MyEnum::FOO), or as an instance ($enum()).

That way, you can use enums as array keys:

'statuses' => [
    TaskStatus::INCOMPLETE() => ['some configuration'],
    TaskStatus::COMPLETED() => ['some configuration'],
],

Or access the underlying primitives for any other use cases:

public function updateStatus(int $status): void;

$task->updateStatus(TaskStatus::COMPLETED());

The main point: this is all without having to append ->value to everything.

This approach also has decent IDE support. You get autosuggestions while typing, and then you just append ():

MyEnum::FOO; // => MyEnum instance
MyEnum::FOO(); // => 1

Apply the trait on your enum

use ArchTech\Enums\InvokableCases;

enum TaskStatus: int
{
    use InvokableCases;

    case INCOMPLETE = 0;
    case COMPLETED = 1;
    case CANCELED = 2;
}

enum Role
{
    use InvokableCases;

    case ADMINISTRATOR;
    case SUBSCRIBER;
    case GUEST;
}

Use static calls to get the primitive value

TaskStatus::INCOMPLETE(); // 0
TaskStatus::COMPLETED(); // 1
TaskStatus::CANCELED(); // 2
Role::ADMINISTRATOR(); // 'ADMINISTRATOR'
Role::SUBSCRIBER(); // 'SUBSCRIBER'
Role::GUEST(); // 'GUEST'

Invoke instances to get the primitive value

public function updateStatus(TaskStatus $status, Role $role)
{
    $this->record->setStatus($status(), $role());
}

Names

This helper returns a list of case names in the enum.

Apply the trait on your enum

use ArchTech\Enums\Names;

enum TaskStatus: int
{
    use Names;

    case INCOMPLETE = 0;
    case COMPLETED = 1;
    case CANCELED = 2;
}

enum Role
{
    use Names;

    case ADMINISTRATOR;
    case SUBSCRIBER;
    case GUEST;
}

Use the names() method

TaskStatus::names(); // ['INCOMPLETE', 'COMPLETED', 'CANCELED']
Role::names(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']

Values

This helper returns a list of case values for backed enums, or a list of case names for pure enums (making this functionally equivalent to ::names() for pure Enums)

Apply the trait on your enum

use ArchTech\Enums\Values;

enum TaskStatus: int
{
    use Values;

    case INCOMPLETE = 0;
    case COMPLETED = 1;
    case CANCELED = 2;
}

enum Role
{
    use Values;

    case ADMINISTRATOR;
    case SUBSCRIBER;
    case GUEST;
}

Use the values() method

TaskStatus::values(); // [0, 1, 2]
Role::values(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']

Options

This helper returns an associative array of case names and values for backed enums, or a list of names for pure enums (making this functionally equivalent to ::names() for pure Enums).

Apply the trait on your enum

use ArchTech\Enums\Options;

enum TaskStatus: int
{
    use Options;

    case INCOMPLETE = 0;
    case COMPLETED = 1;
    case CANCELED = 2;
}

enum Role
{
    use Options;

    case ADMINISTRATOR;
    case SUBSCRIBER;
    case GUEST;
}

Use the options() method

TaskStatus::options(); // ['INCOMPLETE' => 0, 'COMPLETED' => 1, 'CANCELED' => 2]
Role::options(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']

From

This helper adds from() and tryFrom() to pure enums, and adds fromName() and tryFromName() to all enums.

Important Notes:

  • BackedEnum instances already implement their own from() and tryFrom() methods, which will not be overridden by this trait. Attempting to override those methods in a BackedEnum causes a fatal error.
  • Pure enums only have named cases and not values, so the from() and tryFrom() methods are functionally equivalent to fromName() and tryFromName()

Apply the trait on your enum

use ArchTech\Enums\From;

enum TaskStatus: int
{
    use From;

    case INCOMPLETE = 0;
    case COMPLETED = 1;
    case CANCELED = 2;
}

enum Role
{
    use From;

    case ADMINISTRATOR;
    case SUBSCRIBER;
    case GUEST;
}

Use the from() method

Role::from('ADMINISTRATOR'); // Role::ADMINISTRATOR
Role::from('NOBODY'); // Error: ValueError

Use the tryFrom() method

Role::tryFrom('GUEST'); // Role::GUEST
Role::tryFrom('NEVER'); // null

Use the fromName() method

TaskStatus::fromName('INCOMPLETE'); // TaskStatus::INCOMPLETE
TaskStatus::fromName('MISSING'); // Error: ValueError
Role::fromName('SUBSCRIBER'); // Role::SUBSCRIBER
Role::fromName('HACKER'); // Error: ValueError

Use the tryFromName() method

TaskStatus::tryFromName('COMPLETED'); // TaskStatus::COMPLETED
TaskStatus::tryFromName('NOTHING'); // null
Role::tryFromName('GUEST'); // Role::GUEST
Role::tryFromName('TESTER'); // null

Metadata

This trait lets you add metadata to enum cases.

Apply the trait on your enum

use ArchTech\Enums\Metadata;
use ArchTech\Enums\Meta\Meta;
use App\Enums\MetaProperties\{Description, Color};

#[Meta(Description::class, Color::class)]
enum TaskStatus: int
{
    use Metadata;

    #[Description('Incomplete Task')] #[Color('red')]
    case INCOMPLETE = 0;

    #[Description('Completed Task')] #[Color('green')]
    case COMPLETED = 1;

    #[Description('Canceled Task')] #[Color('gray')]
    case CANCELED = 2;
}

Explanation:

  • Description and Color are userland class attributes — meta properties
  • The #[Meta] call enables those two meta properties on the enum
  • Each case must have a defined description & color (in this example)

Access the metadata

TaskStatus::INCOMPLETE->description(); // 'Incomplete Task'
TaskStatus::COMPLETED->color(); // 'green'

Creating meta properties

Each meta property (= attribute used on a case) needs to exist as a class.

#[Attribute]
class Color extends MetaProperty {}

#[Attribute]
class Description extends MetaProperty {}

Inside the class, you can customize a few things. For instance, you may want to use a different method name than the one derived from the class name (Description becomes description() by default). To do that, override the method() method on the meta property:

#[Attribute]
class Description extends MetaProperty
{
    public static function method(): string
    {
        return 'note';
    }
}

With the code above, the description of a case will be accessible as TaskStatus::INCOMPLETE->note().

Another thing you can customize is the passed value. For instance, to wrap a color name like text-{$color}-500, you'd add the following transform() method:

#[Attribute]
class Color extends MetaProperty
{
    protected function transform(mixed $value): mixed
    {
        return "text-{$color}-500";
    }
}

And now the returned color will be correctly transformed:

TaskStatus::COMPLETED->color(); // 'text-green-500'

Use the fromMeta() method

TaskStatus::fromMeta(Color::make('green')); // TaskStatus::COMPLETED
TaskStatus::fromMeta(Color::make('blue')); // Error: ValueError

Use the tryFromMeta() method

TaskStatus::tryFromMeta(Color::make('green')); // TaskStatus::COMPLETED
TaskStatus::tryFromMeta(Color::make('blue')); // null

Recommendation: use annotations and traits

If you'd like to add better IDE support for the metadata getter methods, you can use @method annotations:

/**
 * @method string description()
 * @method string color()
 */
#[Meta(Description::class, Color::class)]
enum TaskStatus: int
{
    use Metadata;

    #[Description('Incomplete Task')] #[Color('red')]
    case INCOMPLETE = 0;

    #[Description('Completed Task')] #[Color('green')]
    case COMPLETED = 1;

    #[Description('Canceled Task')] #[Color('gray')]
    case CANCELED = 2;
}

And if you're using the same meta property in multiple enums, you can create a dedicated trait that includes this @method annotation.

Development

Run all checks locally:

./check

Code style will be automatically fixed by php-cs-fixer.

Comments
  • Added

    Added "Invokable Cases" PHPStan extension

    This includes a PHPStan extension to add support for InvokableCases so that static analysis tools can understand the callable methods and their return types.

    The extension has been added in a way that will allow multiple extensions in the future if required, with a single include file that will import all extensions, or the option to include only specific extensions.

    opened by samlev 11
  • Add valueOptions() to options trait

    Add valueOptions() to options trait

    normally it is the value that get stored in database

    it is usually also the value that is more display friendly in select field as it doesn't have those _ or ALL_CAPS

    Since redefining options() might be a breaking change, i propose valueOptions()

    Let me know if this should be in the Values trait instead.

    opened by ziming 7
  • Laravel package

    Laravel package

    It would be great if a separate package is made for laravel.

    e.g. Command for enum annotate method tag

      /**
       * @method static SUPER_ADMIN()
       * @method static ADMIN()
       */
      enum RoleName: string
      {
          use InvokableCases;
      
          case SUPER_ADMIN = 'SUPER_ADMIN';
          case ADMIN = 'ADMIN';
      }
    
    opened by suleymanozev 4
  • Issue #4: Added `From` trait

    Issue #4: Added `From` trait

    Looking at @stancl's suggestion in Issue #4, I've added the From trait.

    This adds the from() and tryFrom() methods to pure enums, and the fromName() and tryFromName() methods for all enum types.

    opened by samlev 4
  • Optimize InvokableCases to memoize results and remove case looping

    Optimize InvokableCases to memoize results and remove case looping

    There is a high likelihood that cases will be invoked many-many times per-request. Currently, it loops over all cases to find the one it needs on each invocation. In cases where the same enum case is invoked hundreds or thousands of times (e.g. when reading DB rows in), this causes unnecessary work. This removes the loop by using reflection, and memoizes the results.

    While the performance improvement will likely be unnoticeable, and you could consider this a micro-optimization, it is about 2x as fast as the current implementation. The real benefit is code re-use, as after it finds the enum case, it invokes it so the name/value logic is in 1 place.

    opened by dhrrgn 3
  • fromName() & tryFromName()

    fromName() & tryFromName()

    To instantiate a backed enum using the case name.

    Perhaps passing a callback would also work, if transformations or comparisons are needed (e.g. I have a use case that does strtoupper() to convert the readable version to the case name).

    opened by stancl 2
  • Extend functionality to work with pure enums

    Extend functionality to work with pure enums

    This extends the Enum traits to also work in a meaninful manner with "Pure" enums, as well as Backed Enums.

    A couple of the methods kind of "repeat" functionality, but this makes pure enums as useful as string-backed enums, where the string value is equivalent to the case name.

    opened by samlev 0
  • Add an Enum::toOptions() method

    Add an Enum::toOptions() method

    This method would essentially map over each variant/case and return an associative array of ->name => ->value. Super useful for creating dropdowns for enums, think status enums, role enums etc.

    opened by ryangjchandler 0
  • Add an Enum::values() helper method

    Add an Enum::values() helper method

    To get a list of all scalar values for an enum, you'd currently have to loop over all ::cases() and get the value.

    Having a helper for this would be sweet.

    opened by ryangjchandler 0
  • interface implementations

    interface implementations

    would it be good to implement the interface to identify if the enum has the trait Values, Names..?

    Example

    use ArchTech\Enums\Names;
    use ArchTech\Enums\Traits\Nameable;
    
    enum TaskStatus: int implements Nameable
    {
        use Names;
    
        case INCOMPLETE = 0;
        case COMPLETED = 1;
        case CANCELED = 2;
    }
    
    ```
    opened by nicxlau 1
  • Trait for string conversion

    Trait for string conversion

    For enum fields, I like to keep a comment in the database that has all the enum values and text. It might be useful to have a Trait with a toString() to get this? I personally also include a $nameFirst parameter to specify if I want:

    1 => Admin, 2 => Instructor, 3 => Student or Admin => 1, Instructor => 2, Student =>3

    Thanks for the great package!

    opened by gmgarrison 5
  • Support defining #[Meta] on traits

    Support defining #[Meta] on traits

    That way, users can create a trait such as:

    /**
     * @method string description()
     */
    #[Meta(Description::class)
    trait HasDescription
    {}
    

    And use this across their enums as:

    enum Status
    {
    +   use HasDescription;
    
    +   #[Description('Completed Task')]
        case COMPLETED;
    }
    
    enum Role
    {
    +   use HasDescription;
    
    +   #[Description('Administrator')]
        case ADMIN;
    
    +   #[Description('Read-only user')]
        case GUEST;
    }
    

    Implementation

    Edit ArchTech\Enums\Meta\Reflection::metaProperties() to also recursively walk through the enum's traits, and extract the enabled meta properties in their #[Meta] attributes, if they're defined.

    good first issue help wanted 
    opened by stancl 0
Releases(v0.3.1)
  • v0.3.1(Aug 24, 2022)

    What's Changed

    • Added "Invokable Cases" PHPStan extension by @samlev in https://github.com/archtechx/enums/pull/13

    Full Changelog: https://github.com/archtechx/enums/compare/v0.3.0...v0.3.1

    Source code(tar.gz)
    Source code(zip)
  • v0.3.0(Mar 29, 2022)

    What's Changed

    • use array_column() to get the names, values, and options by @ejunker in https://github.com/archtechx/enums/pull/3
    • Extend functionality to work with pure enums by @samlev in https://github.com/archtechx/enums/pull/6
    • Issue #4: Added From trait by @samlev in https://github.com/archtechx/enums/pull/7
    • Metadata by @stancl in https://github.com/archtechx/enums/pull/8

    New Contributors

    • @ejunker made their first contribution in https://github.com/archtechx/enums/pull/3
    • @samlev made their first contribution in https://github.com/archtechx/enums/pull/6
    • @stancl made their first contribution in https://github.com/archtechx/enums/pull/8

    Full Changelog: https://github.com/archtechx/enums/compare/v0.2.0...v0.3.0

    Source code(tar.gz)
    Source code(zip)
Owner
ARCHTECH
Meticulously architected web applications.
ARCHTECH
Support for PHP 8.1 enums in Doctrine.

Doctrine Native Enums This library provides first-class support to PHP Enums, introduced in PHP 8.1, within your Doctrine entities. Installation compo

Beno!t POLASZEK 14 Dec 15, 2022
A collection of standards as PHP Enums: ISO3166, ISO4217, ISO639...

Standards A collection of standards as PHP Enums Setup Make sure you are running PHP 8.1 or higher to use this package To start right away, run the fo

null 295 Dec 20, 2022
This library can be used, among other things, to retrieve the classes, interfaces, traits, enums, functions and constants declared in a file

marijnvanwezel/reflection-file Library that allows reflection of files. This library can be used, among other things, to retrieve the classes, interfa

Marijn van Wezel 5 Apr 17, 2022
DiscordLookup | Get more out of Discord with Discord Lookup! Snowflake Decoder, Guild List with Stats, Invite Info and more...

DiscordLookup Get more out of Discord with Discord Lookup! Snowflake Decoder, Guild List with Stats, Invite Info and more... Website Getting Help Tool

Felix 69 Dec 23, 2022
Ratio plugin is a luck plugin. The more lucky you are, the more you win!

Ratio Ratio plugin is a luck plugin. The more lucky you are, the more you win Features When you break a block (Cobblestone), it gives/puts you somethi

Ali Tura Çetin 2 Apr 25, 2022
Making phone calls with PHP and Twilio Voice Service.

TwilioVoice-PHP This is an example implementation of Twilio's phone call API. You can clone this code to your project: git clone https://github.com/P

Philipe  Lima 2 Nov 15, 2022
Orkestra is a library of infrastructure and architecture helpers for creating CQRS applications

Orkestra Orkestra is an opinionated framework with a plethora of recommendations on architectural design that we use internally at Morebec to develop

Morébec 2 Aug 18, 2021
Adds a compact "easy-sort" mode to Repeater and Repeater Matrix, making those fields easier to sort when there are a large number of items.

Repeater Easy Sort Adds a compact "easy-sort" mode to Repeater and Repeater Matrix, making those fields easier to sort when there are a large number o

Robin Sallis 3 Oct 10, 2021
Contains a few tools usefull for making your test-expectations agnostic to operating system specifics

PHPUnit Tools to ease cross operating system Testing make assertEquals* comparisons end-of-line (aka PHP_EOL) character agnostic Make use of EolAgnost

Markus Staab 1 Jan 3, 2022
Preload your sweet sweet code to opcache with a composer command, making your code faster to run.

Composer Preload Preload your sweet sweet code to opcache with a composer command, making your code run faster. Composer Preload is a composer plugin

Ayesh Karunaratne 197 Dec 6, 2022
Naive Bayes works by looking at a training set and making a guess based on that set.

Naive Bayes Naive Bayes works by looking at a training set and making a guess based on that set. It uses simple statistics and a bit of math to calcul

Assisted Mindfulness 29 Nov 27, 2022
Integrate reCAPTCHA using async HTTP/2, making your app fast with a few lines.

ReCaptcha Integrate reCAPTCHA using async HTTP/2, making your app fast with a few lines. use Illuminate\Support\Facades\Route; Route::post('login', f

Laragear 14 Dec 6, 2022
Simple custom chat bot developing framework for telegram, qq and more in PHP (the best language)

RinoBot RinoBot 是一个为统一聊天机器人扩展开发的框架,编写一份插件用于多种机器人协议。 简体中文 | English ?? 开发中 ?? 暂不适用于生产环境 特性 插件扩展机制 一份代码运行于多平台多协议机器人 并减小开发难度 插件提供 Yaml 配置 供使用者修改 基于机器人 We

LixWorth 3 Apr 18, 2022
A package for adding more type safety to your PHP projects.

Table of Contents Overview Installation Usage Simple Checks Advanced Checks Custom Checks Skipping Checks Testing Security Contribution Credits Change

Ash Allen 14 Aug 31, 2022
A lot of scripts and packages in modern PHP demand one or more configuration classes

A lot of scripts and packages in modern PHP demand one or more configuration classes. Mostly, those are a set of properties that can be set, changed or retrieved. However, some of the configurations have a peculiar behaviour - such as boolean properties.

Carlos Artur Curvelo da Silva Matos 2 Mar 8, 2022
php String Objects Chains like length,forEach,filter,replace,repalcAll much More.... Module

php String Objects Chains like length,forEach,filter,replace,repalcAll much More.... Module

im__koli 1 Mar 29, 2022
QuidPHP/Main is a PHP library that provides a set of base objects and collections that can be extended to build something more specific.

QuidPHP/Main is a PHP library that provides a set of base objects and collections that can be extended to build something more specific. It is part of the QuidPHP package and can also be used standalone.

QuidPHP 4 Jul 2, 2022
PHP library with basic objects and more for working with Facebook/Metas Conversions API

PHP library with basic objects and more for working with Facebook/Metas Conversions API Installation The easiest way to install this library is by ins

null 5 Dec 5, 2022
Allow multiple options for Magento 2 checkout layout. Provides capabilities to AB test checkout changes and more.

Aimes_CheckoutDesigns Features Please note: This module is currently still considered a proof of concept. This module provides the ability to change c

Rob Aimes 30 Aug 8, 2022