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
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
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.
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 ?
myclabs/php-enum
provides a base enum implementation as a class, inspired from \SplEnum
. However, it doesn't provide as many features nor integrations as we wish to.commerceguys/enum
only acts as a utility class, but does not intend to instantiate enumerations. Hence, it doesn't allow as many features nor integrations with third-party libraries and frameworks. Manipulating enums as objects is also one of the first motivations of this project.yethee/BiplaneEnumBundle
is the first library we got inspiration from. But it was designed as a Symfony Bundle, whereas we opted for a component first approach. Integrations are then provided in a dedicated Bridge
namespace and are not restricted to Symfony.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.
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.
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 implementEnumInterface::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.
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 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
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
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.
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.
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)";
}
Then, you'll simply need to register your DBAL type:
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);
refs:
p/config/config.yml
rine:
dbal:
types:
gender: AppBundle\Doctrine\DBAL\Types\GenderEnumType
mapping_types:
gender: string
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;
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.
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 }]
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,
],
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).
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.
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| |———–|———-| |||
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:
EnumProvider::enum(string $enumValueShortcut): EnumInterface
in order to generate deterministic enumsEnumProvider::randomEnum(string $enumClassAlias): EnumInterface
in order to generate random enumsIf 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 theCivility
enum class, not to a constant's value ('mister' string for instance).
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.
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.
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.