Elao/PhpEnums

Name: PhpEnums

Owner: Elao

Description: :nut_and_bolt: Provides enumerations for PHP & frameworks integrations

Created: 2016-11-03 15:45:37.0

Updated: 2018-01-12 16:10:08.0

Pushed: 2017-12-15 16:39:02.0

Homepage: null

Size: 319

Language: PHP

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Elao Enumerations

Latest Stable Version Total Downloads Monthly Downloads Build Status Coveralls Scrutinizer Code Quality php

This project is greatly inspired by the BiplaneEnumBundle and aims to provide the missing PHP enumerations support.

It will leverage integration of the main PHP frameworks and also provide bridges with other libraries when relevant.

Table of Contents

Why?

An enumeration is a data type, enclosing a single value from a predictable set of members (enumerators). Each enumerator name is a single identifier, materialized by a PHP constant.

Using an enum class provides many benefits:

Enumerations are not options and are not meant to replace constants. Designing an enum type should be done by keeping your domain in mind, and should convey a strong meaning on your application logic.

Wrong use-cases:

Valid use-cases:

Why another library ?

Finally, we used to create similar classes from scratch in some projects lately.
Providing our own package inspired from the best ones, on which we'll apply our own philosophy looks a better way to go.

Features

Installation

mposer require elao/enum

Even if you're using the Symfony full stack framework, there is nothing more to do.
We won't register anything in the Symfony DIC for now. Simply use provided classes.

Usage

Declare your own enumeration by creating a class extending Elao\Enum\Enum:

p

Elao\Enum\Enum;

l class Gender extends Enum

const UNKNOW = 'unknown';
const MALE = 'male';
const FEMALE = 'female';

public static function values(): array
{
    return [
        self::UNKNOW, 
        self::MALE, 
        self::FEMALE
    ];
}

? It's recommended to make your enums classes final, because it won't make sense to extend them in most situations (unless you're creating a new base enum type), and you won't need to mock an enum type.

? You can also use the AutoDiscoveredValuesTrait to automagically guess values from the constants defined in your enum, so you don't have to implement EnumInterface::values() yourself.

Get an instance of your enum type:

p
m = Gender::get(Gender::Male);

You can easily retrieve the enumeration's value by using $enum->getValue();

? Enum values are supposed to be integers or strings.

Readable enums

Sometimes, enums may be displayed to the user, or exported in a human readable way.
Hence comes the ReadableEnum:

p

Elao\Enum\ReadableEnum;

l class Gender extends ReadableEnum

const UNKNOW = 'unknown';
const MALE = 'male';
const FEMALE = 'female';

public static function values(): array
{
    return [
        self::UNKNOW,
        self::MALE,
        self::FEMALE,
    ];
}

public static function readables(): array
{
    return [
        self::UNKNOW => 'Unknown',
        self::MALE => 'Male',
        self::FEMALE => 'Female',
    ];
}

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

p
m = Gender::get(Gender::Male); 
m->getReadable(); // returns 'Male'
ing) $enum; // returns 'Male'

If you're using a translation library, you can also simply return translation keys from the ReadableEnumInterface::readables() method:

p

Elao\Enum\ReadableEnum;

l class Gender extends ReadableEnum

// ...

public static function readables(): array
{
    return [
        self::UNKNOW => 'enum.gender.unknown',
        self::MALE => 'enum.gender.male',
        self::FEMALE => 'enum.gender.female',
    ];
}

Using Symfony's translation component:

pp/Resources/translations/messages.en.yml
m.gender.unknown: 'Unknown'
m.gender.male: 'Male'
m.gender.female: 'Female'
hp
p
m = Gender::get(Gender::MALE);
et translator instance...
nslator->trans($enum); // returns 'Male'
Flagged enums

Flagged enumerations are used for bitwise operations. Each value of the enumeration is a single bit flag and can be combined together into a valid bitmask in a single enum instance.

p

Elao\Enum\FlaggedEnum;

l class Permissions extends FlaggedEnum

const EXECUTE = 1;
const WRITE = 2;
const READ = 4;

// You can declare shortcuts for common bit flag combinations
const ALL = self::EXECUTE | self::WRITE | self::READ;

public static function values(): array
{
    return [
        // Only declare valid bit flags:
        static::EXECUTE,
        static::WRITE,
        static::READ,
    ];
}

public static function readables(): array
{
    return [
        static::EXECUTE => 'Execute',
        static::WRITE => 'Write',
        static::READ => 'Read',

        // You can define readable values for specific bit flag combinations:
        static::WRITE | static::READ => 'Read & write',
        static::EXECUTE | static::READ => 'Read & execute',
        static::ALL => 'All permissions',
    ];
}

Get instances using bitwise operations and manipulate them:

p
missions = Permissions::get(Permissions::EXECUTE | Permissions::WRITE | Permissions::READ);
missions = $permissions->withoutFlags(Permissions::EXECUTE); // Returns an instance without "execute" flag
missions->getValue(); // Returns 6 (int)
missions->getFlags(); // Returns [2, 4] (=> [Permissions::EXECUTE, Permissions::WRITE])

missions = $permissions->withoutFlags(Permissions::READ | Permissions::WRITE); // Returns an instance without "read" and "write" flags
missions->getValue(); // Returns Permissions::NONE (0). Note: NONE is defined in parent class, FlaggedEnum.
missions->getFlags(); // Returns an empty array

missions = Permissions::get(Permissions::NONE); // Returns an empty bitmask instance
missions = $permissions->withFlags(Permissions::READ | Permissions::EXECUTE); // Returns an instance with "read" and "execute" permissions
missions->hasFlag(Permissions::READ); // True
missions->hasFlag(Permissions::READ | Permissions::EXECUTE); // True
missions->hasFlag(Permissions::WRITE); // False
Compare

Enumeration values are singletons (exact term in this case actually is multiton): it means you'll always get the exact same instance for a given value. Thus, in order to compare two instances, you can simply use the strict comparison operator in order to check references:

p
er::get(Gender::MALE) === Gender::get(Gender::FEMALE); // False
er::get(Gender::MALE) === Gender::get(Gender::MALE); // True
issions::get(Permissions::ALL) === Permissions::get(
Permissions::READ | Permissions::WRITE | Permissions::EXECUTE
/ True

You can also override the EnumInterface::equals(EnumInterface $enumToCompare) in order to implement your own logic to determine if two instances should be considered the same.
The default implementation compares both enum type (the class) and value.

p
er::get(Gender::MALE)->equals(Gender::get(Gender::FEMALE)) // False
er::get(Gender::MALE)->equals(Gender::get(Gender::MALE)) // True

Lastly, you can simply compare an instance with a value by using the EnumInterface::is($value):

p
er::get(Gender::MALE)->is(Gender::FEMALE) // False
er::get(Gender::MALE)->is(Gender::MALE) // True
Shortcuts

Inspired from myclabs/php-enum, you can use shortcuts to instantiate your enumerations, thanks to PHP's __callStatic magic method:

p
er::MALE(); // Returns an instance of Gender with the MALE value

We recommend you to use this method, if and only if, you and your team use an IDE (e.g PhpStorm) able to interpret the @method tag in class definitions. Then, you can benefit from IDE completion by declaring the following:

p


method static Gender UNKNOW()
method static Gender MALE()
method static Gender FEMALE()

l class Gender extends ReadableEnum

const UNKNOW = 'unknown';
const MALE = 'male';
const FEMALE = 'female';

// ...

Otherwise, simply implement the static methods yourself.

Integrations

Doctrine

You can store the raw value of an enumeration in the database, but still manipulate it as an object from your entities by creating a custom DBAL type, from scratch.

However, this library can help you by providing abstract classes for both string and integer based enumerations.

Create the DBAL type

First, create your DBAL type by extending either AbstractEnumType (string based enum) or AbstractIntegerEnumType (integer based enum, for flagged enums for instance):

p

Elao\Enum\Bridge\Doctrine\DBAL\Types\AbstractEnumType;

l class GenderEnumType extends AbstractEnumType

const NAME = 'gender';

protected function getEnumClass(): string
{
    return Gender::class;
}

public function getName()
{
    return static::NAME;
}

Note: You can map to native sql enum type by overriding declaration method:

public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
    $values = implode(', ', array_map(function ($value) { return "'$value'";}, Gender::values()));

    return "ENUM($values)";
}
Register the DBAL type

Then, you'll simply need to register your DBAL type:

Manually
p
n bootstrapping code
..
Doctrine\DBAL\Types\Type;
::addType(GenderEnumType::NAME, GenderEnumType::class);

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

p
n = $em->getConnection();
n->getDatabasePlatform()->registerDoctrineTypeMapping(GenderEnumType::NAME, GenderEnumType::class);
Using the Doctrine Bundle with Symfony

refs:

p/config/config.yml
rine:
dbal:
    types:
        gender:  AppBundle\Doctrine\DBAL\Types\GenderEnumType
    mapping_types:
        gender: string
Mapping

When registering the custom types in the configuration, you specify a unique name for the mapping type and map it to the corresponding fully qualified class name. Now the new type can be used when mapping columns:

p
s User

/** @Column(type="gender") */
private $gender;

Default value on null

Two methods allow to set a default value if null is retrieved from the database, or before persisting a value:

p

ract class AbstractEnumType extends Type

// ...

/**
 * What should be returned on null value from the database.
 *
 * @return mixed
 */
protected function onNullFromDatabase()
{
    return null;
}

/**
 * What should be returned on null value from PHP.
 *
 * @return mixed
 */
protected function onNullFromPhp()
{
    return null;
}

Override those methods in order to satisfy your needs.

Symfony Serializer component

Available for Symfony 2.8 and 3.1+

Simply register the following normalizer inside the DIC configuration:

rvices.yml
ices:
app.enum_normalizer:
    class: 'Elao\Enum\Bridge\Symfony\Serializer\Normalizer\EnumNormalizer'
    public: false
    tags: [{ name: serializer.normalizer }]
Symfony Form component

Available for Symfony 2.8 and 3.1+

Simple enums

Simply use the EnumType:

p

Elao\Enum\Bridge\Symfony\Form\Type\EnumType;
MyApp\Enum\Gender;

..

lder->add('gender', EnumType::class, [
'enum_class' => Gender::class,


..

m->submit($data);
m->get('gender')->getData(); // Will return a `Gender` instance (or null)

Only the enum_class option is required.

You can use any ChoiceType option as usual (for instance the multiple option).

The field data will be an instance of your enum. If you only want to map values, you can use the as_value option:

p

Elao\Enum\Bridge\Symfony\Form\Type\EnumType;
MyApp\Enum\Gender;

..

lder->add('gender', EnumType::class, [
'enum_class' => Gender::class,
'as_value' => true,


..

m->submit($data);
m->get('gender')->getData(); // Will return a string value defined in the `Gender` enum (or null)

You can restrict the list of proposed enumerations by overriding the choices option:

p

Elao\Enum\Bridge\Symfony\Form\Type\EnumType;
MyApp\Enum\Gender;

..

lder->add('gender', EnumType::class, [
'enum_class' => Gender::class,
'choices' => [
    Gender::get(Gender::MALE), 
    Gender::get(Gender::FEMALE),
],


r:

lder->add('gender', EnumType::class, [
'enum_class' => Gender::class,
'as_value' => true,
'choices' => [
    Gender::readableFor(Gender::MALE) => Gender::MALE,
    Gender::readableFor(Gender::FEMALE) => Gender::FEMALE,
],

Flagged enums

Simply use the FlaggedEnumType (which extends EnumType):

p

Elao\Enum\Bridge\Symfony\Form\Type\FlaggedEnumType;
MyApp\Enum\Permissions;

..

lder->add('permissions', FlaggedEnumType::class, [
'enum_class' => Permissions::class,


..

m->submit($data);
m->get('permissions')->getData(); // Will return a single `Permissions` instance composed of selected bit flags

Same options are available, but on the contrary of the EnumType, the multiple option is always true and cannot be set to false (You'll always get a single enum instance though).

Symfony Validator component

Available for Symfony 2.8 and 3.1+

The library provides a Elao\Enum\Bridge\Symfony\Validator\Constraint\Enum constraint which makes use of Symfony's built-in Choice constraint and validator internally.

To use the constraint, simply provide the enum class:

c/AppBundle/Resources/config/validation.yml
undle\Entity\User:
properties:
    gender:
        - Elao\Enum\Bridge\Symfony\Validator\Constraint\Enum: MyApp\Enum\Gender

If the property value is not an enum instance, set the asValue option to true in order to simply validate the enum value:

c/AppBundle/Resources/config/validation.yml
undle\Entity\User:
properties:
    gender:
        - Elao\Enum\Bridge\Symfony\Validator\Constraint\Enum:
            class: MyApp\Enum\Gender
            asValue: true

You can restrict the available choices by setting the allowed values in the choices option:

c/AppBundle/Resources/config/validation.yml
undle\Entity\User:
properties:
    gender:
        - Elao\Enum\Bridge\Symfony\Validator\Constraint\Enum:
            class: MyApp\Enum\Gender
            choices: 
                - female
                - !php/const:MyApp\Enum\Gender::MALE # You can use PHP constants with the YAML format since Symfony 3.2

The choice option only accepts enum values and normalize it internally to enum instances if asValue is false.

You can also use a callback:

c/AppBundle/Resources/config/validation.yml
undle\Entity\User:
properties:
    gender:
        - Elao\Enum\Bridge\Symfony\Validator\Constraint\Enum:
            class: MyApp\Enum\Gender
            callback: ['allowedValues']

Where allowedValues is a static method of MyApp\Enum\Gender, returning allowed instances (:warning: should return values if asValue is set to true).

Any other Choice option (as multiple, min, …) is available with the Enum constraint.

Symfony VarDumper component

Available for Symfony 2.8 and 3.1+

By requiring this package and if symfony/var-dumper is installed, an EnumCaster is registered automatically to enhance enum instances dump output.

For instance, here's what it'll look like when dumping a flagged enum instance:

p

Elao\Enum\Tests\Fixtures\Enum\Permissions;

(Permissions::get(Permissions::ALL));

|HTML output|CLI output| |———–|———-| |var-dumper-integration-cli|var-dumper-integration-cli|

Faker

The PhpEnums library provides an Elao\Enum\Bridge\Faker\Provider\EnumProvider to generate fixtures.

Its constructor receives a mapping between class aliases and your Enum classes' FQCN as first parameter:

p

Elao\Enum\Bridge\Faker\Provider\EnumProvider;

vider = new EnumProvider([
'Civility' => Namespace\To\MyCivilityEnum::class,
'Gender' => Namespace\To\MyGenderEnum::class,

The provider exposes two public methods:

Usage with Alice

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

rvices.yml
ices:
app.faker.enum_provider:
    class: Elao\Enum\Bridge\Faker\Provider\EnumProvider
    arguments:
        - Civility: Namespace\To\MyCivilityEnum
          Gender: Namespace\To\MyGenderEnum
    tags: [{ name: nelmio_alice.faker.provider }]

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

tity:
entity1:
    civility: <enum(Civility::MISTER)>
    gender: <enum(Gender::MALE>
    # You can use the pipe character in order to combine flagged enums:
    permissions: <enum(Permissions::READ|WRITE>
entity2:
    civility: <randomEnum(Civility)>
    gender: <randomEnum(Gender)>
    permissions: <randomEnum(Permissions)>

? MISTER in <enum(Civility::MISTER)> refers to a constant defined in the Civility enum class, not to a constant's value ('mister' string for instance).

API

Simple enum

Method | Static | Returns | Description —— | —— | ——- | ———– get($value) | Yes | static| Returns the instance of the enum type for given value. values() | Yes | int[]|string[] | Should return any possible value for the enumeration. accepts($value) | Yes | bool | True if the value is acceptable for this enumeration. instances() | Yes | static[] | Instantiates and returns an array containing every enumeration instance for possible values. getValue() | No | int|string | Returns the enumeration instance value. equals(EnumInterface $enum) | No | bool | Determines whether two enumerations instances should be considered the same. is($value) | No | bool | Determines if the enumeration instance value is equal to the given value.

Readable enum

Method | Static | Returns | Description —— | —— | ——- | ———– readables() | Yes | string[] | Should return an array of the human representations indexed by possible values. readableFor($value) | Yes | string | Get the human representation for given enumeration value. getReadable() | No | string | Get the human representation for the current instance. __toString() | No | string | Allows to convert the instance to the human representation of the current value by casting it to a string.

Flagged enum

Method | Static | Returns | Description —— | —— | ——- | ———– accepts($value) | Yes | bool | Same as before, but accepts bit flags and bitmasks. readableForNone() | Yes | string | Override this method to replace the default human representation of the “no flag” value. readableFor($value, string $separator = '; ') | Yes | string | Same as before, but allows to specify a delimiter between single bit flags (if no human readable representation is found for the combination). getReadable(string $separator = '; ') | No | string | Same as before, but with a delimiter option (see above). getFlags() | No | int[] | Returns an array of bit flags set in the current enumeration instance. hasFlag(int $bitFlag) | No | bool | True if the current instance has the given bit flag(s). withFlags(int $flags) | No | static | Computes a new value with given flags, and returns the corresponding instance. withoutFlags(int $flags) | No | static | Computes a new value without given flags, and returns the corresponding instance.


This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.