Skip to content
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,5 @@ testbench.yaml
/docs
/coverage
auth.json

.DS_Store
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"php": "^8.2",
"laravel/prompts": "^0.3",
"spatie/laravel-package-tools": "^1.16",
"illuminate/contracts": "^10.0||^11.0||^12.0"
"illuminate/contracts": "^10.0||^11.0||^12.0",
"illuminate/support": "^10.0||^11.0||^12.0"
},
"require-dev": {
"laravel/pint": "^1.14",
Expand Down
2 changes: 2 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ parameters:
tmpDir: build/phpstan
checkOctaneCompatibility: true
checkModelProperties: true
ignoreErrors:
- '#Trait DevWizardHQ\\Enumify\\Concerns\\EnumHelpers is used zero times and is not analysed#'
94 changes: 94 additions & 0 deletions src/Concerns/EnumHelpers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

declare(strict_types=1);

namespace DevWizardHQ\Enumify\Concerns;

/**
* Provides common utility methods for backed enums.
*
* Use this trait on any string-backed or int-backed enum to gain
* helpers for listing options, values, names, and checking existence.
*
* @mixin \BackedEnum
*/
trait EnumHelpers
{
/**
* Get all enum cases as an array of value => label pairs.
*
* Calls the given method name on each case (defaults to "label").
* Falls back to a humanized version of the case name when the method does not exist.
*
* Note: PHP coerces numeric-string keys to integers in arrays.
* Use {@see selectOptions()} if your enum has numeric-like string values.
*
* @return array<string|int, string>
*/
public static function options(string $label = 'label'): array
{
return collect(self::cases())
->mapWithKeys(fn (self $case): array => [
$case->value => method_exists($case, $label)
? $case->{$label}()
: self::humanize($case->name),
])
->all();
Comment thread
iqbalhasandev marked this conversation as resolved.
}
Comment thread
iqbalhasandev marked this conversation as resolved.

/**
* Get all enum cases as an array of {value, label} objects for frontend selects.
*
* Falls back to a humanized version of the case name when the method does not exist.
*
* @return array<int, array{value: string|int, label: string}>
*/
public static function selectOptions(string $label = 'label'): array
{
return collect(self::cases())
->map(fn (self $case): array => [
'value' => $case->value,
'label' => method_exists($case, $label)
? $case->{$label}()
: self::humanize($case->name),
])
Comment thread
iqbalhasandev marked this conversation as resolved.
->values()
->all();
}

/**
* Get all enum values as an array.
*
* @return array<int, string|int>
*/
public static function values(): array
{
return array_column(self::cases(), 'value');
}

/**
* Get all enum names as an array.
*
* @return array<int, string>
*/
public static function names(): array
{
return array_column(self::cases(), 'name');
}

/**
* Check if a value exists in this enum.
*/
public static function hasValue(string|int $value): bool
{
return self::tryFrom($value) !== null;
}

/**
* Convert a SCREAMING_SNAKE_CASE name to a human-readable title.
*/
private static function humanize(string $name): string
{
return ucwords(strtolower(str_replace('_', ' ', $name)));
}
Comment thread
iqbalhasandev marked this conversation as resolved.
}
Comment thread
iqbalhasandev marked this conversation as resolved.
3 changes: 3 additions & 0 deletions tests/Fixtures/CampusStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@

namespace DevWizardHQ\Enumify\Tests\Fixtures;

use DevWizardHQ\Enumify\Concerns\EnumHelpers;

/**
* Fixture: Backed enum with custom methods (color, isActive, etc.).
* This demonstrates full method extraction for TypeScript generation.
*/
enum CampusStatus: string
{
use EnumHelpers;
case ACTIVE = 'active';
case SUSPENDED = 'suspended';
case INACTIVE = 'inactive';
Expand Down
3 changes: 3 additions & 0 deletions tests/Fixtures/OrderStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

namespace DevWizardHQ\Enumify\Tests\Fixtures;

use DevWizardHQ\Enumify\Concerns\EnumHelpers;

/**
* Fixture: Backed enum with string values.
*/
enum OrderStatus: string
{
use EnumHelpers;
case PENDING = 'pending';
case PROCESSING = 'processing';
case SHIPPED = 'shipped';
Expand Down
2 changes: 2 additions & 0 deletions tests/Fixtures/PaymentMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

namespace DevWizardHQ\Enumify\Tests\Fixtures;

use DevWizardHQ\Enumify\Concerns\EnumHelpers;
use DevWizardHQ\Enumify\Contracts\HasLabels;

/**
* Fixture: Backed enum with per-case label() method.
*/
enum PaymentMethod: string implements HasLabels
{
use EnumHelpers;
case CREDIT_CARD = 'credit_card';
case DEBIT_CARD = 'debit_card';
case BANK_TRANSFER = 'bank_transfer';
Expand Down
104 changes: 104 additions & 0 deletions tests/Unit/EnumHelpersTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<?php

declare(strict_types=1);

use DevWizardHQ\Enumify\Tests\Fixtures\CampusStatus;
use DevWizardHQ\Enumify\Tests\Fixtures\OrderStatus;
use DevWizardHQ\Enumify\Tests\Fixtures\PaymentMethod;

it('returns options as value => label pairs', function () {
$options = PaymentMethod::options();

expect($options)->toBe([
'credit_card' => 'Credit Card',
'debit_card' => 'Debit Card',
'bank_transfer' => 'Bank Transfer',
'paypal' => 'PayPal',
'crypto' => 'Cryptocurrency',
]);
});

it('returns options using a custom method name', function () {
$options = CampusStatus::options('color');

expect($options)->toBe([
'active' => 'green',
'suspended' => 'red',
'inactive' => 'gray',
]);
});

it('falls back to humanized case name when label method does not exist', function () {
$options = OrderStatus::options();

expect($options)->toBe([
'pending' => 'Pending',
'processing' => 'Processing',
'shipped' => 'Shipped',
'delivered' => 'Delivered',
'cancelled' => 'Cancelled',
]);
});

it('returns select options as value/label arrays', function () {
$options = PaymentMethod::selectOptions();

expect($options)->toBe([
['value' => 'credit_card', 'label' => 'Credit Card'],
['value' => 'debit_card', 'label' => 'Debit Card'],
['value' => 'bank_transfer', 'label' => 'Bank Transfer'],
['value' => 'paypal', 'label' => 'PayPal'],
['value' => 'crypto', 'label' => 'Cryptocurrency'],
]);
});

it('returns select options using a custom method name', function () {
$options = CampusStatus::selectOptions('color');

expect($options)->toBe([
['value' => 'active', 'label' => 'green'],
['value' => 'suspended', 'label' => 'red'],
['value' => 'inactive', 'label' => 'gray'],
]);
});

it('falls back to humanized case name in select options when method does not exist', function () {
$options = OrderStatus::selectOptions();

expect($options)->toBe([
['value' => 'pending', 'label' => 'Pending'],
['value' => 'processing', 'label' => 'Processing'],
['value' => 'shipped', 'label' => 'Shipped'],
['value' => 'delivered', 'label' => 'Delivered'],
['value' => 'cancelled', 'label' => 'Cancelled'],
]);
});

it('returns all enum values', function () {
expect(PaymentMethod::values())->toBe([
'credit_card',
'debit_card',
'bank_transfer',
'paypal',
'crypto',
]);
});

it('returns all enum names', function () {
expect(PaymentMethod::names())->toBe([
'CREDIT_CARD',
'DEBIT_CARD',
'BANK_TRANSFER',
'PAYPAL',
'CRYPTO',
]);
});

it('checks if a value exists', function () {
expect(PaymentMethod::hasValue('credit_card'))->toBeTrue();
expect(PaymentMethod::hasValue('nonexistent'))->toBeFalse();
});

it('uses strict comparison for hasValue', function () {
expect(PaymentMethod::hasValue(''))->toBeFalse();
});