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 all commits
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 @@ -186,6 +187,65 @@ TaskStatus::options(); // ['INCOMPLETE' => 0, 'COMPLETED' => 1, 'CANCELED' => 2]
Role::options(); // ['ADMINISTRATOR', 'SUBSCRIBER', 'GUEST']
```

### 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 only have named cases and not values, so the `from()` and `tryFrom()` methods are functionally equivalent to `fromName()` and `tryFromName()`

#### 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('ADMINISTRATOR'); // Role::ADMINISTRATOR
Role::from('NOBODY'); // Error: ValueError
```

#### Use the `tryFrom()` method
```php
Role::tryFrom('GUEST'); // Role::GUEST
Role::tryFrom('NEVER'); // null
```

#### Use the `fromName()` method
```php
TaskStatus::fromName('INCOMPLETE'); // TaskStatus::INCOMPLETE
TaskStatus::fromName('MISSING'); // Error: ValueError
Role::fromName('SUBSCRIBER'); // Role::SUBSCRIBER
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
55 changes: 55 additions & 0 deletions src/From.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace ArchTech\Enums;

use ValueError;

trait From
{
/**
* Gets the Enum by name, if it exists, for "Pure" enums.
*
* This will not override the `from()` method on BackedEnums
*
* @throws ValueError
*/
public static function from(string $case): static
samlev marked this conversation as resolved.
Show resolved Hide resolved
{
return static::fromName($case);
}

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

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

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

return array_values($cases)[0] ?? null;
}
}
5 changes: 3 additions & 2 deletions 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,15 +52,15 @@ 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;
use InvokableCases, Options, Names, Values, From;

case ADMIN;
case GUEST;
Expand Down
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 name with from() for pure enums')
->expect(Role::from('ADMIN'))
->toBe(Role::ADMIN);

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

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

it('can returns null when selecting a non-existent case by name with tryFrom() for pure enums')
->expect(Role::tryFrom('NOBODY'))
->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('NOBODY');
})->throws(ValueError::class, '"NOBODY" 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('NOBODY'))
->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);