Skip to content

Commit

Permalink
introduced PHP 8.0 Attribute enumerations
Browse files Browse the repository at this point in the history
  • Loading branch information
thunderer committed Jun 6, 2022
1 parent d7344e2 commit 89c4ceb
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ jobs:
run: 'php vendor/bin/psalm --no-cache --shepherd'
continue-on-error: '${{ matrix.failure }}'
- name: 'Infection'
run: 'php vendor/bin/infection -j2 --min-msi=98'
run: 'php vendor/bin/infection -j2 --min-msi=97'
continue-on-error: '${{ matrix.failure }}'
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ travis-job:
${PHP} php vendor/bin/psalm --no-cache

qa-psalm:
${PHP} php vendor/bin/psalm --no-cache
${PHP} php vendor/bin/psalm --no-cache --find-unused-psalm-suppress
qa-psalm-suppressed:
grep -rn psalm-suppress src

Expand Down
69 changes: 45 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,71 +90,92 @@ bin/generate static Thunder\\Platenum\\YourEnum FOO,BAR=3

There are multiple sources from which Platenum can read enumeration members. Base `EnumTrait` provides all enum functionality without any source, to be defined in a static `resolve()` method. Each source is available both as a `trait` which uses `EnumTrait` with concrete `resolve()` method implementation and an `abstract class` based on that trait. Usage of traits is recommended as target enum classes should not have any common type hint.

In this section the `BooleanEnum` class with two members (`TRUE=true` and `FALSE=false`) will be used as an example.
In this section the `StatusEnum` class with two members (`ACTIVE=1` and `DISABLED=2`) will be used as an example.

**class constants**

```php
final class BooleanEnum
final class StatusEnum
{
use ConstantsEnumTrait;

private const TRUE = true;
private const FALSE = false;
private const ACTIVE = 1;
private const DISABLED = 2;
}
```

```php
final class BooleanEnum extends AbstractConstantsEnum
final class StatusEnum extends AbstractConstantsEnum
{
private const TRUE = true;
private const FALSE = false;
private const ACTIVE = 1;
private const DISABLED = 2;
}
```

**class docblock**

> Note: There is no way to specify members values inside docblock, therefore all members names are also their values - in this case `TRUE='TRUE'` and `FALSE='FALSE'`.
> Note: There is no way to specify members values inside docblock, therefore all members names are also their values - in this case `ACTIVE='ACTIVE'` and `DISABLED='DISABLED'`.
```php
/**
* @method static static TRUE()
* @method static static FALSE()
* @method static static ACTIVE()
* @method static static DISABLED()
*/
final class BooleanEnum
final class StatusEnum
{
use DocblockEnumTrait;
}
```

```php
/**
* @method static static TRUE()
* @method static static FALSE()
* @method static static ACTIVE()
* @method static static DISABLED()
*/
final class BooleanEnum extends AbstractDocblockEnum {}
final class StatusEnum extends AbstractDocblockEnum {}
```

**attributes (PHP 8.0)**

Leverage PHP 8.0 features by declaring members through attributes:

```php
#[Member('ACTIVE', 1)]
#[Member('DISABLED', 2)]
final class StatusEnum
{
use AttributeEnumTrait;
}
```

```php
use Thunder\Platenum\Enum\Member;

#[Member(member: 'ACTIVE', value: 1)]
#[Member(member: 'DISABLED', value: 2)]
final class StatusEnum extends AbstractAttributeEnum {}
```

**static property**

```php
final class BooleanEnum
final class StatusEnum
{
use StaticEnumTrait;

private static $mapping = [
'TRUE' => true,
'FALSE' => false,
'ACTIVE' => 1,
'DISABLED' => 2,
];
}
```

```php
final class BooleanEnum extends AbstractStaticEnum
final class StatusEnum extends AbstractStaticEnum
{
private static $mapping = [
'TRUE' => true,
'FALSE' => false,
'ACTIVE' => 1,
'DISABLED' => 2,
];
}
```
Expand Down Expand Up @@ -199,15 +220,15 @@ Currency::initialize(fn() => json_decode(file_get_contents('...')));
> Note: The `resolve` method will be called only once when the enumeration is used for the first time.
```php
final class BooleanEnum
final class StatusEnum
{
use EnumTrait;

private static function resolve(): array
{
return [
'TRUE' => true,
'FALSE' => false,
'ACTIVE' => 1,
'DISABLED' => 2,
];
}
}
Expand Down Expand Up @@ -281,7 +302,7 @@ There are already a few `enum` libraries in the PHP ecosystem. Why another one?

**Sources** Platenum allows multiple sources for enumeration members. `EnumTrait` contains all enum functions - extend it with your custom `resolve()` method to create custom source. In fact, all enumeration sources in this repository are defined this way.

**Features** Platenum provides complete feature set for all kinds of operations on enumeration members, values, comparison, transformation, and more. Look at PhpEnumerations project to see the feature matrix created during development of this library.
**Features** Platenum provides complete feature set for all kinds of operations on enumeration members, values, comparison, transformation, and more. Look at [PhpEnumerations](https://github.com/thunderer/PhpEnumerations) project to see the feature matrix created during development of this library.

**Inheritance** Existing solutions use inheritance for creating enum classes:

Expand Down
2 changes: 1 addition & 1 deletion psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<psalm
errorLevel="1"
resolveFromConfigFile="true"
findUnusedPsalmSuppress="true"
findUnusedPsalmSuppress="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd">
Expand Down
11 changes: 11 additions & 0 deletions src/Enum/AbstractAttributeEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Thunder\Platenum\Enum;

/**
* @author Tomasz Kowalczyk <[email protected]>
*/
abstract class AbstractAttributeEnum implements \JsonSerializable
{
use AttributeEnumTrait;
}
26 changes: 26 additions & 0 deletions src/Enum/AttributeEnumTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Thunder\Platenum\Enum;

/**
* @author Tomasz Kowalczyk <[email protected]>
*/
trait AttributeEnumTrait
{
use EnumTrait;

/** @psalm-suppress UndefinedDocblockClass, UndefinedMethod because there is no ReflectionAttribute on PHP <8.0 */
private static function resolve(): array
{
/** @var \ReflectionAttribute[] $attributes */
$attributes = (new \ReflectionClass(static::class))->getAttributes(Member::class);
$members = [];
foreach($attributes as $attribute) {
/** @var Member $member */
$member = $attribute->newInstance();
$members[$member->member] = $member->value;
}

return $members;
}
}
2 changes: 1 addition & 1 deletion src/Enum/EnumTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ final public static function fromEnum($enum): self

/**
* @param static $enum
* @param-out AbstractConstantsEnum|AbstractDocblockEnum|AbstractStaticEnum|AbstractCallbackEnum $enum
* @param-out AbstractConstantsEnum|AbstractDocblockEnum|AbstractStaticEnum|AbstractCallbackEnum|AbstractAttributeEnum $enum
*/
final public function fromInstance(&$enum): void
{
Expand Down
26 changes: 26 additions & 0 deletions src/Enum/Member.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Thunder\Platenum\Enum;

/**
* @author Tomasz Kowalczyk <[email protected]>
* @psalm-immutable
*/
#[\Attribute(\Attribute::TARGET_CLASS|\Attribute::IS_REPEATABLE)]
final class Member
{
/** @var string */
public $member;
/** @var int|string */
public $value;

/**
* @param string $member
* @param int|string $value
*/
public function __construct($member, $value)
{
$this->member = $member;
$this->value = $value;
}
}
31 changes: 31 additions & 0 deletions tests/AbstractTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
namespace Thunder\Platenum\Tests;

use PHPUnit\Framework\TestCase;
use Thunder\Platenum\Enum\AbstractAttributeEnum;
use Thunder\Platenum\Enum\AbstractCallbackEnum;
use Thunder\Platenum\Enum\AbstractConstantsEnum;
use Thunder\Platenum\Enum\AbstractDocblockEnum;
use Thunder\Platenum\Enum\AbstractStaticEnum;
use Thunder\Platenum\Enum\AttributeEnumTrait;
use Thunder\Platenum\Enum\CallbackEnumTrait;
use Thunder\Platenum\Enum\ConstantsEnumTrait;
use Thunder\Platenum\Enum\DocblockEnumTrait;
use Thunder\Platenum\Enum\EnumTrait;
use Thunder\Platenum\Enum\Member;
use Thunder\Platenum\Enum\StaticEnumTrait;
use Thunder\Platenum\Tests\Fake\FakeEnum;

Expand Down Expand Up @@ -138,6 +141,34 @@ protected function makeCallbackExtendsEnum(array $members): string
return $class;
}

/** @return FakeEnum */
protected function makeAttributeTraitEnum(array $members): string
{
$attributes = [];
foreach($members as $member => $value) {
$attributes[] = '#['.Member::class.'(\''.$member.'\', '.$value.')]';
}

$class = $this->computeUniqueClassName('AttributeTrait');
eval(implode("\n", $attributes)."\n".'final class '.$class.' implements \JsonSerializable { use '.AttributeEnumTrait::class.'; }');

return $class;
}

/** @return FakeEnum */
protected function makeAttributeExtendsEnum(array $members): string
{
$attributes = [];
foreach($members as $member => $value) {
$attributes[] = '#['.Member::class.'(\''.$member.'\', '.$value.')]';
}

$class = $this->computeUniqueClassName('AttributeExtends');
eval(implode("\n", $attributes)."\n".'final class '.$class.' extends '.AbstractAttributeEnum::class.' {}');

return $class;
}

protected function computeUniqueClassName(string $prefix): string
{
while(true === class_exists($class = $prefix.random_int(1, 1000000))) {
Expand Down
25 changes: 25 additions & 0 deletions tests/AttributeEnumTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Thunder\Platenum\Tests;

use Thunder\Platenum\Enum\CallbackEnumTrait;
use Thunder\Platenum\Exception\PlatenumException;

/**
* @author Tomasz Kowalczyk <[email protected]>
*/
final class AttributeEnumTest extends AbstractTestCase
{
public function testMembers(): void
{
PHP_VERSION_ID < 80000 && $this->markTestSkipped('Requires PHP 8.0');

$members = ['FIRST' => 1, 'SECOND' => 2];
$trait = $this->makeAttributeTraitEnum($members);
$extends = $this->makeAttributeExtendsEnum($members);

$expected = ['FIRST' => 1, 'SECOND' => 2];
$this->assertSame($expected, $trait::getMembersAndValues());
$this->assertSame($expected, $extends::getMembersAndValues());
}
}

0 comments on commit 89c4ceb

Please sign in to comment.