• Stars
    star
    324
  • Rank 129,708 (Top 3 %)
  • Language
    PHP
  • License
    MIT License
  • Created about 8 years ago
  • Updated 6 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🔩 Extended PHP 8.1+ enums features & specific integrations with frameworks and libraries

Elao Enumerations

Latest Version Total Downloads Monthly Downloads Tests Coveralls Scrutinizer Code Quality php

Provides additional, opinionated features to the PHP 8.1+ native enums as well as specific integrations with frameworks and libraries.

#[ReadableEnum(prefix: 'suit.')]
enum Suit: string implements ReadableEnumInterface
{
    use ReadableEnumTrait;

    case Hearts = '♥︎';
    case Diamonds = '♦︎';
    case Clubs = '♣︎';
    case Spades = '︎♠︎';
}

📢 This project used to emulate enumerations before PHP 8.1.
For the 1.x documentation, click here

You can also consult this issue to follow objectives & progress for the V2 of this lib.


Installation

composer require "elao/enum:^2.0"

Or, in order to help and test latest changes:

composer require "elao/enum:^2.x-dev"

Readable enums

Readable enums provide a way to expose human-readable labels for your enum cases, by adding a new ReadableEnumInterface contract to your enums.

The easiest way to implement this interface is by using the ReadableEnumTrait and the EnumCase attribute:

namespace App\Enum;

use Elao\Enum\ReadableEnumInterface;
use Elao\Enum\ReadableEnumTrait;
use Elao\Enum\Attribute\EnumCase;

enum Suit: string implements ReadableEnumInterface
{
    use ReadableEnumTrait;

    #[EnumCase('suit.hearts')]
    case Hearts = '♥︎';

    #[EnumCase('suit.diamonds')]
    case Diamonds = '♦︎';

    #[EnumCase('suit.clubs')]
    case Clubs = '♣︎';

    #[EnumCase('suit.spades')]
    case Spades = '︎♠︎';
}

The following snippet shows how to get the human-readable value of an enum:

Suit::Hearts->getReadable(); // returns 'suit.hearts'

It defines a proper contract to expose an enum case label instead of using the enum case internal name. Which is especially useful if the locale to expose labels to your users differs from the one you're writing your code, as well as for creating integrations with libraries requiring to expose such labels.

It's also especially useful in conjunction with a translation library like Symfony's Translation component, by using translation keys.

Given the following translation file:

# translations/messages.fr.yaml
suit.hearts: 'Coeurs'
suit.diamonds: 'Carreaux'
suit.clubs: 'Piques'
suit.spades: 'Trèfles'
$enum = Suit::Hearts;
$translator->trans($enum->getReadable(), locale: 'fr'); // returns 'Coeurs'

Configure suffix/prefix & default value

As a shorcut, you can also use the ReadableEnum attribute to define the common suffix and prefix to use, as well as defaulting on the enum case name or value, if not provided explicitly:

#[ReadableEnum(prefix: 'suit.')]
enum Suit: string implements ReadableEnumInterface
{
    use ReadableEnumTrait;

    #[EnumCase('hearts︎')]
    case Hearts = '♥︎';
    case Diamonds = '♦︎';
    case Clubs = '♣︎';
    case Spades = '︎♠︎';
}

Suit::Hearts->getReadable(); // returns 'suit.hearts'
Suit::Clubs->getReadable(); // returns 'suit.Clubs'

using the case value (only for string backed enums):

#[ReadableEnum(prefix: 'suit.', useValueAsDefault: true)]
enum Suit: string implements ReadableEnumInterface
{
    use ReadableEnumTrait;

    case Hearts = 'hearts';
    case Diamonds = 'diamonds';
    case Clubs = 'clubs︎';
    case Spades = '︎spades';
}

Suit::Hearts->getReadable(); // returns 'suit.hearts'
Suit::Clubs->getReadable(); // returns 'suit.clubs'

Extra values

The EnumCase attributes also provides you a way to configure some extra attributes on your cases and access these easily with the ExtrasTrait:

namespace App\Enum;

use Elao\Enum\ReadableEnumInterface;
use Elao\Enum\ExtrasTrait;
use Elao\Enum\Attribute\EnumCase;

enum Suit implements ReadableEnumInterface
{
    use ExtrasTrait;

    #[EnumCase(extras: ['icon' => 'fa-heart', 'color' => 'red'])]
    case Hearts;

    #[EnumCase(extras: ['icon' => 'fa-diamond', 'color' => 'red'])]
    case Diamonds;

    #[EnumCase(extras: ['icon' => 'fa-club', 'color' => 'black'])]
    case Clubs;

    #[EnumCase(extras: ['icon' => 'fa-spade', 'color' => 'black'])]
    case Spades;
}

Access these infos using ExtrasTrait::getExtra(string $key, bool $throwOnMissingExtra = false): mixed:

Suit::Hearts->getExtra('color'); // 'red'
Suit::Spades->getExtra('icon'); // 'fa-spade'
Suit::Spades->getExtra('missing-key'); // null
Suit::Spades->getExtra('missing-key', true); // throws

or create your own interfaces/traits:

interface RenderableEnumInterface 
{
    public function getColor(): string;
    public function getIcon(): string;
}

use Elao\Enum\ExtrasTrait;

trait RenderableEnumTrait
{
    use ExtrasTrait;

    public function getColor(): string
    {
        $this->getExtra('color', true);
    }
    
    public function getIcon(): string
    {
        $this->getExtra('icon', true);
    }
}

use Elao\Enum\Attribute\EnumCase;

enum Suit implements RenderableEnumInterface
{
    use RenderableEnumTrait;

    #[EnumCase(extras: ['icon' => 'fa-heart', 'color' => 'red'])]
    case Hearts;
    
    // […]
}

Suit::Hearts->getColor(); // 'red'

Flag enums

Flagged enumerations are used for bitwise operations.

namespace App\Enum;

enum Permissions: int
{
    case Execute = 1 << 0;
    case Write = 1 << 1;
    case Read = 1 << 2;
}

Each enumerated case is a bit flag and can be combined with other cases into a bitmask and manipulated using a FlagBag object:

use App\Enum\Permissions;
use Elao\Enum\FlagBag;

$permissions = FlagBag::from(Permissions::Execute, Permissions::Write, Permissions::Read);
// same as:
$permissions = new FlagBag(Permissions::class, 7); 
// where 7 is the "encoded" bits value for:
Permissions::Execute->value | Permissions::Write->value | Permissions::Read->value // 7
// or initiate a bag with all its possible values using:
$permissions = FlagBag::fromAll(Permissions::class);

$permissions = $permissions->withoutFlags(Permissions::Execute); // Returns an instance without "execute" flag

$permissions->getValue(); // Returns 6, i.e: the encoded bits value
$permissions->getBits(); // Returns [2, 4], i.e: the decoded bits
$permissions->getFlags(); // Returns [Permissions::Write, Permissions::Read]

$permissions = $permissions->withoutFlags(Permissions::Read, Permissions::Write); // Returns an instance without "read" and "write" flags
$permissions->getBits(); // Returns []
$permissions->getFlags(); // Returns []

$permissions = new FlagBag(Permissions::class, FlagBag::NONE); // Returns an empty bag

$permissions = $permissions->withFlags(Permissions::Read, Permissions::Execute); // Returns an instance with "read" and "execute" flags

$permissions->hasFlags(Permissions::Read); // True
$permissions->hasFlags(Permissions::Read, Permissions::Execute); // True
$permissions->hasFlags(Permissions::Write); // False

Hence, using FlagBag::getValue() you can get an encoded value for any combination of flags from your enum, and use it for storage or communication between your processes.

Integrations

Symfony Form

Symfony already provides an EnumType for allowing the user to choose one or more options defined in a PHP enumeration.
It extends the ChoiceType field and defines the same options.

However, it uses the enum case name as label, which might not be convenient.
Since this library specifically supports readable enums, it ships its own EnumType, extending Symfony's one and using the human representation of each case instead of their names.

Use it instead of Symfony's one:

namespace App\Form\Type;

use App\Enum\Suit;
use Symfony\Component\Form\AbstractType;
use Elao\Enum\Bridge\Symfony\Form\Type\EnumType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;

class CardType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('suit', EnumType::class, [
                'class' => Suit::class, 
                'expanded' => true,
            ])
        ;
    }

    // ...
}

FlagBag Form Type

If you want to use FlagBag in Symfony Forms, use the FlagBagType. This type also extends Symfony EnumType, but it transforms form values to and from FlagBag instances.

namespace App\Form\Type;

use App\Enum\Permissions;
use Symfony\Component\Form\AbstractType;
use Elao\Enum\Bridge\Symfony\Form\Type\FlagBagType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;

class AuthenticationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('permission', FlagBagType::class, [
                'class' => Permissions::class, 
            ])
        ;
    }

    // ...
}

Symfony HttpKernel

Resolve controller arguments from route path

As of Symfony 6.1+, backed enum cases will be resolved from route path parameters:

class CardController
{
    #[Route('/cards/{suit}')]
    public function list(Suit $suit): Response
    {
        // [...]
    }
}

➜ A call to /cards/H will resolve the $suit argument as the Suit::Hearts enum case.

If you're not yet using Symfony HttpKernel 6.1+, this library will still make this working by registering its own resolver.

Resolve controller arguments from query or body

You can also resolve from query params or from the request body:

use Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\Attributes\BackedEnumFromQuery;

class DefaultController
{
    #[Route('/cards')]
    public function list(
        #[BackedEnumFromQuery]
        ?Suit $suit = null,
    ): Response
    {
        // [...]
    }
}

➜ A call to /cards?suit=H will resolve the $suit argument as the Suit::Hearts enum case.

Use BackedEnumFromBody to resolve from the request body ($_POST).

It also supports variadics:

use Elao\Enum\Bridge\Symfony\HttpKernel\Controller\ArgumentResolver\Attributes\BackedEnumFromQuery;

class DefaultController
{
    #[Route('/cards')]
    public function list(
        #[BackedEnumFromQuery]
        ?Suit ...$suits = null,
    ): Response
    {
        // [...]
    }
}

➜ A call to /cards?suits[]=H&suits[]=S will resolve the $suits argument as [Suit::Hearts, Suit::Spades].

Doctrine

As of doctrine/orm 2.11, PHP 8.1 enum types are supported natively:

#[Entity]
class Card
{
    #[Column(type: 'string', enumType: Suit::class)]
    public $suit;
}

Note: Unless you have specific needs for a DBAL type as described below, we recommend using the official ORM integration for backed enums.

PhpEnums however also provides some base classes to save your PHP backed enumerations in your database. Custom DBAL classes for use-cases specific to this library, such as storing a flag bag or a collection of backed enum cases, are or will also be available.

In a Symfony app

This configuration is equivalent to the following sections explaining how to create a custom Doctrine DBAL type:

elao_enum:
  doctrine:
    types:
      App\Enum\Suit: ~ # Defaults to `{ class: App\Enum\Suit, default: null, type: single }`
      permissions: { class: App\Enum\Permission } # You can set a name different from the enum FQCN
      permissions_bag: { class: App\Enum\Permissions, type: flagbag } # values are stored as an int and retrieved as FlagBag object
      App\Enum\RequestStatus: { default: 200 } # Default value from enum cases, in case the db value is NULL

It'll actually generate & register the types classes for you, saving you from writing this boilerplate code.

Manually

Read the Doctrine DBAL docs first.

Extend the AbstractEnumType:

namespace App\Doctrine\DBAL\Type;

use Elao\Enum\Bridge\Doctrine\DBAL\Types\AbstractEnumType;
use App\Enum\Suit;

class SuitType extends AbstractEnumType
{
    protected function getEnumClass(): string
    {
        return Suit::class; // By default, the enum FQCN is used as the DBAL type name as well
    }
}

In your application bootstrapping code:

use App\Doctrine\DBAL\Type\SuitType;
use Doctrine\DBAL\Types\Type;

Type::addType(Suit::class, SuitType::class);

To convert the underlying database type of your new "Suit" type directly into an instance of Suit when performing schema operations, the type has to be registered with the database platform as well:

$conn = $em->getConnection();
$conn->getDatabasePlatform()->registerDoctrineTypeMapping(Suit::class, SuitType::class);

Then, use it as a column type:

use App\Enum\Suit;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Card
{
    #[ORM\Column(Suit::class, nullable: false)]
    private Suit $field;
}

Troubleshooting

Using enum instances with a QueryBuilder

When using enum instance as parameters in a query made with Doctrine\ORM\QueryBuilder and generated DBAL types from the bundle, parameter type might not be inferred correctly.

Either explicitly use enum value instead of an instance, or pass the registered DBAL type as the 3rd parameter in setParameter() to allow the query builder to cast the object to the database value correctly.

I.E, given:

#[ORM\Entity]
class Card
{
    #[ORM\Column(Suit::class, nullable: true)
    protected ?Suit $suit = null;
}

Use one of the following methods:

private function findByType(?Suit $suit = null): array 
{
    $qb = $em->createQueryBuilder()
      ->select('c')
      ->from('Card', 'c')
      ->where('c.suit = :suit');
      
    // use a value from constants:
    $qb->setParameter('param1', Suit::SPADES->value);
    
    // or from instances:
    $qb->setParameter('suit', $suit->value);  
    // Use the 3rd parameter to set the DBAL type
    $qb->setParameter('suit', $suit, Suit::class);
    
    // […]
}   

Doctrine ODM

You can store enumeration values as string or integer in your MongoDB database and manipulate them as objects thanks to custom mapping types included in this library.

In a near future, custom ODM classes for use-cases specific to this library, such as storing a flag bag or a collection of backed enum cases, would also be provided.

In a Symfony app

This configuration is equivalent to the following sections explaining how to create a custom Doctrine ODM type:

elao_enum:
  doctrine_mongodb:
    types:
      App\Enum\Suit: ~ # Defaults to `{ class: App\Enum\Suit, type: single }`
      permissions: { class: App\Enum\Permission } # You can set a name different from the enum FQCN
      another: { class: App\Enum\AnotherEnum, type: collection } # values are stored as an array of integers or strings
      App\Enum\RequestStatus: { default: 200 } # Default value from enum cases, in case the db value is NULL

It'll actually generate & register the types classes for you, saving you from writing this boilerplate code.

Manually

Read the Doctrine ODM docs first.

Extend the AbstractEnumType or AbstractCollectionEnumType:

namespace App\Doctrine\ODM\Type;

use Elao\Enum\Bridge\Doctrine\ODM\Types\AbstractEnumType;
use App\Enum\Suit;

class SuitType extends AbstractEnumType
{
    protected function getEnumClass(): string
    {
        return Suit::class; // By default, the enum FQCN is used as the DBAL type name as well
    }
}

In your application bootstrapping code:

use App\Doctrine\ODM\Type\SuitType;
use Doctrine\ODM\MongoDB\Types\Type;

Type::addType(Suit::class, SuitType::class);

Mapping

Now the new type can be used when mapping fields:

use App\Enum\Suit;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;

#[MongoDB\Document]
class Card
{
    #[MongoDB\Field(Suit::class)]
    private Suit $field;
}

Faker

The PhpEnums library provides a faker EnumProvider allowing to select random enum cases:

use \Elao\Enum\Bridge\Faker\Provider\EnumProvider;
 
$faker = new Faker\Generator();
$faker->addProvider(new EnumProvider());

$faker->randomEnum(Suit::class) // Select one of the Suit cases, e.g: `Suit::Hearts`
$faker->randomEnums(Suit::class, 2, min: 1) // Select between 1 and 2 enums cases, e.g: `[Suit::Hearts, Suit::Spades]`
$faker->randomEnums(Suit::class, 3, exact: true) // Select exactly 3 enums cases

Its constructor receives a mapping of enum types aliases as first argument:

new EnumProvider([
    'Civility' => App\Enum\Civility::class,
    'Suit' => App\Enum\Suit::class,
]);

This is especially useful when using this provider with Nelmio Alice's DSL (see next section)

Usage with Alice

If you're using the nelmio/alice package and its bundle in order to generate fixtures, you can register the Faker provider by using the nelmio_alice.faker.generator:

# config/services.yaml
services:
    Elao\Enum\Bridge\Faker\Provider\EnumProvider:
        arguments:
            - Civility: App\Enum\Civility
              Suit: App\Enum\Suit
        tags: ['nelmio_alice.faker.provider']

The following example shows how to use the provider within a PHP fixture file:

return [
    MyEntity::class => [
        'entity1' => [
            'civility' => Civility::MISTER // Select a specific case, using PHP directly
            'suit' => '<randomEnum(App\Enum\Suit)>' // Select a random case
            'suit' => '<randomEnum(Suit)>' // Select a random case, using the FQCN alias
            'permissions' => '<randomEnums(Permissions, 3, false, 1)>' // Select between 1 and 2 enums cases
            'permissions' => '<randomEnums(Permissions, 3, true)>' // Select exactly 3 enums cases
        ]
    ]
]

More Repositories

1

WebProfilerExtraBundle

Adding routing, container, assetic & twig information in the web profiler
HTML
262
star
2

ErrorNotifierBundle

This bundle send you an email when an error appear (500 or 404 if enable)
PHP
58
star
3

ElaoFormTranslationBundle

Provides a nice way of generating translation keys for form fields
PHP
44
star
4

ElaoJsonHttpFormBundle

Adds support for JSON POST requests to Symfony Forms
PHP
35
star
5

accesseo

Provide accessibility and SEO insights of your page in Symfony profiler
PHP
29
star
6

symfony-standard

[DEPRECATED] Our version of the Symfony standard edition
PHP
19
star
7

github-agile-dashboard

📉 Github Agile Dashboard
JavaScript
19
star
8

ElaoBrowserDetectorBundle

PHP
16
star
9

FormSimpleObjectMapper

🖇 Eases mapping immutable/value objects to Symfony Forms
PHP
16
star
10

elao_

Elao website & blog
Twig
8
star
11

ElaoAdminBundle

Elao Admin Bundle
PHP
8
star
12

ElaoMceMediaBundle

This bundle handles the integration of File Manager and Image Manager plugins inside TinyMCE
PHP
6
star
13

blog

ELAO Team blog - https://blog.elao.com
CSS
6
star
14

ElaoFormBundle

Tools & enhancements for Symfony 2 forms
PHP
6
star
15

tricot

Jeu de tricot de Noël
JavaScript
5
star
16

CommandMigration

Library to run migration commands, for example on deployment
PHP
5
star
17

form.js

Form JS utilities.
JavaScript
5
star
18

container.js

Microscopic dependency injection container for Javascript.
JavaScript
5
star
19

osx-installer

Script to easily setup our dev environment
4
star
20

AcmeApp

React Native Skeleton demo
Objective-C
4
star
21

ReactNativeRealmConnect

Connect React Native components to Realm queries
JavaScript
4
star
22

acetone

Wear gloves!
JavaScript
4
star
23

CmsSlotBundle

Bundle to handle editable cms slot on any page
JavaScript
3
star
24

BootstrapBundle

Bootstrap bundle integration
JavaScript
3
star
25

Facebook

Facebook PHP SDK
PHP
3
star
26

MailjetBundle

Mailjet Integration Bundle
PHP
3
star
27

ElaoConsentBundle

This bundle provides a "cookies toast" allowing you to require user consent before using tracking scripts or cookies.
PHP
2
star
28

ElaoThemeTwitterBootstrap3Bundle

Elao Theme Twitter Bootstrap 3
HTML
2
star
29

symfony-standard-extension

JavaScript
2
star
30

ElaoThemeBundle

Elao Theme Bundle
PHP
2
star
31

ElaoRestActionBundle

REST actions for ElaoAdminBundle
PHP
2
star
32

elao-admin

📊 📈 🔑 ⚙️
SCSS
2
star
33

rest-framework

Base components to build REST API
JavaScript
1
star
34

ca-mache-quoi

Projet interne dédié à la satisfaction culinaire de la team Elao.
TypeScript
1
star
35

VoucherAuthenticationBundle

Provide authentication through vouchers (for email link).
PHP
1
star
36

ElaoBrowserDetector

PHP
1
star
37

meteor-admin-generator

Scaffolding generator for Meteor elao:meteor-admin package
JavaScript
1
star
38

stylelint-formatter-relative-junit

JavaScript
1
star
39

ElaoThemeElaoStrapBundle

Elao Theme ElaoStrap Bundle
PHP
1
star
40

manala-ci-action

Provision & run commands inside a Manala CI environment with "elao.app" recipe in your Github Actions workflows
1
star
41

talk-stream

Elao Talk streaming how to and config
1
star