Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #4: Added From trait #7

Merged
merged 6 commits into from
Mar 23, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ A collection of enum helpers for PHP.
- [`Names`](#names)
- [`Values`](#values)
- [`Options`](#options)
- [`From`](#from)

You can read more about the idea on [Twitter](https://twitter.com/archtechx/status/1495158228757270528). I originally wanted to include the `InvokableCases` helper in [`archtechx/helpers`](https://github.com/archtechx/helpers), but it makes more sense to make it a separate dependency and use it *inside* the other package.

Expand Down Expand Up @@ -144,6 +145,65 @@ enum TaskStatus: int
TaskStatus::options(); // ['INCOMPLETE' => 0, 'COMPLETED' => 1, 'CANCELED' => 2]
```

### 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 do not actually have an internal index, so rearranging the order of cases will not break other code but it will create inconsistent behaviour with the `from()` and `tryFrom()` methods
samlev marked this conversation as resolved.
Show resolved Hide resolved

#### Apply the trait on your enum
```php
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
```php
Role::from(0); // Role::ADMINISTRATOR
Role::from(5); // Error: \ValueError
samlev marked this conversation as resolved.
Show resolved Hide resolved
```

#### Use the `tryFrom()` method
```php
Role::tryFrom(2); // Role::GUEST
Role::tryFrom(5); // null
```

#### Use the `fromName()` method
```php
TaskStatus::fromName('INCOMPLETE'); // TaskStatus::INCOMPLETE
Role::fromName('SUBSCRIBER'); // Role::SUBSCRIBER
TaskStatus::fromName('MISSING'); // Error: \ValueError
Role::fromName('HACKER'); // Error: \ValueError
```

#### Use the `tryFromName()` method
```php
TaskStatus::tryFromName('COMPLETED'); // TaskStatus::COMPLETED
TaskStatus::tryFromName('NOTHING'); // null
Role::tryFromName('GUEST'); // Role::GUEST
Role::tryFromName('TESTER'); // null
```

## Development

Run all checks locally:
Expand Down
53 changes: 53 additions & 0 deletions src/From.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace ArchTech\Enums;

trait From
{
/**
* Gets the Enum by index for "Pure" enums.
*
* This will not override the `from()` method on BackedEnums
*
* @throws \ValueError
*/
static public function from(int $case): static
samlev marked this conversation as resolved.
Show resolved Hide resolved
{
return static::cases()[$case] ?? throw new \ValueError($case . ' is not a valid unit for enum "' . get_called_class() . '"');
samlev marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Gets the Enum by index, if it exists for "Pure" enums.
*
* This will not override the `tryFrom()` method on BackedEnums
*/
static public function tryFrom(int $case): ?static
{
return static::cases()[$case] ?? null;
}

/**
* Gets the Enum by name.
*
* @throws \ValueError
*/
static public function fromName(string $case): static
{
return static::tryFromName($case) ?? throw new \ValueError('"' . $case . '" is not a valid name for enum "' . get_called_class() . '"');
}

/**
* Gets the Enum by name, if it exists.
*/
static public function tryFromName(string $case): ?static
{
$cases = array_filter(
static::cases(),
fn ($c) => $c->name === $case
);

return array_pop($cases) ?? null;
samlev marked this conversation as resolved.
Show resolved Hide resolved
}
}
11 changes: 10 additions & 1 deletion tests/Pest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
|
*/

use ArchTech\Enums\From;
use ArchTech\Enums\InvokableCases;
use ArchTech\Enums\Names;
use ArchTech\Enums\Options;
Expand Down Expand Up @@ -51,8 +52,16 @@ function something()

enum Status: int
{
use InvokableCases, Options, Names, Values;
use InvokableCases, Options, Names, Values, From;

case PENDING = 0;
case DONE = 1;
}

enum Role
{
use InvokableCases, Options, Names, Values, From;

case ADMIN;
case GUEST;
}
65 changes: 65 additions & 0 deletions tests/Pest/FromTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

it('does not override the default BackedEnum from method')
->expect(Status::from(0))
->toBe(Status::PENDING);

it('does not override the default BackedEnum from method with errors', function () {
Status::from(2);
})->throws(\ValueError::class, '2 is not a valid backing value for enum "Status"');

it('does not override the default BackedEnum tryFrom method')
->expect(Status::tryFrom(1))
->toBe(Status::DONE);

it('does not override the default BackedEnum tryFrom method with errors')
->expect(Status::tryFrom(2))
->toBe(null);

it('can select a case by index with from() for pure enums')
->expect(Role::from(0))
->toBe(Role::ADMIN);

it('throws a value error when selecting a non-existent case by index with from() for pure enums', function () {
Role::from(2);
})->throws(\ValueError::class, '2 is not a valid unit for enum "Role"');

it('can select a case by index with tryFrom() for pure enums')
->expect(Role::tryFrom(1))
->toBe(Role::GUEST);

it('can returns null when selecting a non-existent case by index with tryFrom() for pure enums')
->expect(Role::tryFrom(2))
->toBe(null);

it('can select a case by name with fromName() for pure enums')
->expect(Role::fromName('ADMIN'))
->toBe(Role::ADMIN);

it('throws a value error when selecting a non-existent case by name with fromName() for pure enums', function () {
Role::fromName('NOTHING');
})->throws(\ValueError::class, '"NOTHING" is not a valid name for enum "Role"');

it('can select a case by name with tryFromName() for pure enums')
->expect(Role::tryFromName('GUEST'))
->toBe(Role::GUEST);

it('returns null when selecting a non-existent case by name with tryFromName() for pure enums')
->expect(Role::tryFromName('NOTHING'))
->toBe(null);

it('can select a case by name with fromName() for backed enums')
->expect(Status::fromName('PENDING'))
->toBe(Status::PENDING);

it('throws a value error when selecting a non-existent case by name with fromName() for backed enums', function () {
Status::fromName('NOTHING');
})->throws(\ValueError::class, '"NOTHING" is not a valid name for enum "Status"');

it('can select a case by name with tryFromName() for backed enums')
->expect(Status::tryFromName('DONE'))
->toBe(Status::DONE);

it('returns null when selecting a non-existent case by name with tryFromName() for backed enums')
->expect(Status::tryFromName('NOTHING'))
->toBe(null);