Coder Social home page Coder Social logo

enum's Introduction

PHP Enum

Latest Version on Packagist License Postcardware

PHP from Packagist Build Status Total Downloads

This package offers strongly typed enums in PHP. In this package, enums are always objects, never constant values on their own. This allows for proper static analysis and refactoring in IDEs.

Here's how enums are created with this package:

use \Spatie\Enum\Enum;

/**
 * @method static self draft()
 * @method static self published()
 * @method static self archived()
 */
class StatusEnum extends Enum
{
}

And this is how they are used:

public function setStatus(StatusEnum $status): void
{
    $this->status = $status;
}

// ...

$class->setStatus(StatusEnum::draft());

Support us

We invest a lot of resources into creating best in class open source packages. You can support us by buying one of our paid products.

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on our contact page. We publish all received postcards on our virtual postcard wall.

Installation

You can install the package via composer:

composer require spatie/enum

Usage

This is how an enum can be defined.

/**
 * @method static self draft()
 * @method static self published()
 * @method static self archived()
 */
class StatusEnum extends Enum
{
}

This is how they are used:

public function setStatus(StatusEnum $status)
{
    $this->status = $status;
}

// ...

$class->setStatus(StatusEnum::draft());
Autocompletion Refactoring

Creating an enum from a value

$status = StatusEnum::from('draft');

When an enum value doesn't exist, you'll get a BadMethodCallException. If you would prefer not catching an exception, you can use:

$status = StatusEnum::tryFrom('draft');

When an enum value doesn't exist in this case, $status will be null.

The only time you want to construct an enum from a value is when unserializing them from eg. a database.

If you want to get the value of an enum to store it, you can do this:

$status->value;

Note that value is a read-only property, it cannot be changed.

Enum values

By default, the enum value is its method name. You can however override it, for example if you want to store enums as integers in a database, instead of using their method name.

/**
 * @method static self draft()
 * @method static self published()
 * @method static self archived()
 */
class StatusEnum extends Enum
{
    protected static function values(): array
    {
        return [
            'draft' => 1,
            'published' => 2,
            'archived' => 3,
        ];
    }
}

An enum value doesn't have to be a string, as you can see in the example it can also be an int.

Note that you don't need to override all values. Rather, you only need to override the ones that you want to be different from the default.

If you have a logic that should be applied to all method names to get the value, like lowercase them, you can return a Closure.

/**
 * @method static self DRAFT()
 * @method static self PUBLISHED()
 * @method static self ARCHIVED()
 */
class StatusEnum extends Enum
{
    protected static function values(): Closure
    {
        return function(string $name): string|int {
            return mb_strtolower($name);
        };
    }
}

Enum labels

Enums can be given a label, you can do this by overriding the labels method.

/**
 * @method static self draft()
 * @method static self published()
 * @method static self archived()
 */
class StatusEnum extends Enum
{
    protected static function labels(): array
    {
        return [
            'draft' => 'my draft label',
        ];
    }
}

Note that you don't need to override all labels, the default label will be the enum's value.

If you have a logic that should be applied to all method names to get the label, like lowercase them, you can return a Closure as in the value example.

You can access an enum's label like so:

$status->label;

Note that label is a read-only property, it cannot be changed.

Comparing enums

Enums can be compared using the equals method:

$status->equals(StatusEnum::draft());

You can pass several enums to the equals method, it will return true if the current enum equals one of the given values.

$status->equals(StatusEnum::draft(), StatusEnum::archived());

Phpunit Assertions

This package provides an abstract class Spatie\Enum\Phpunit\EnumAssertions with some basic/common assertions if you have to test enums.

use Spatie\Enum\Phpunit\EnumAssertions;

EnumAssertions::assertIsEnum($post->status); // checks if actual extends Enum::class
EnumAssertions::assertIsEnumValue(StatusEnum::class, 'draft'); // checks if actual is a value of given enum
EnumAssertions::assertIsEnumLabel(StatusEnum::class, 'draft'); // checks if actual is a label of given enum
EnumAssertions::assertEqualsEnum(StatusEnum::draft(), 'draft'); // checks if actual (transformed to enum) equals expected
EnumAssertions::assertSameEnum(StatusEnum::draft(), $post->status); // checks if actual is same as expected
EnumAssertions::assertSameEnumValue(StatusEnum::draft(), 1); // checks if actual is same value as expected
EnumAssertions::assertSameEnumLabel(StatusEnum::draft(), 'draft'); // checks if actual is same label as expected

Faker Provider

Possibly you are using faker and want to generate random enums. Because doing so with default faker is a lot of copy'n'paste we've got you covered with a faker provider Spatie\Enum\Faker\FakerEnumProvider.

use Spatie\Enum\Faker\FakerEnumProvider;
use Faker\Generator as Faker;

/** @var Faker|FakerEnumProvider $faker */
$faker = new Faker();
$faker->addProvider(new FakerEnumProvider($faker));

$enum = $faker->randomEnum(StatusEnum::class);
$value = $faker->randomEnumValue(StatusEnum::class);
$label = $faker->randomEnumLabel(StatusEnum::class);

Testing

composer test

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Security

If you've found a bug regarding security please mail [email protected] instead of using the issue tracker.

Postcardware

You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using.

Our address is: Spatie, Kruikstraat 22, 2018 Antwerp, Belgium.

We publish all received postcards on our company website.

Credits

License

The MIT License (MIT). Please see License File for more information.

enum's People

Contributors

adrianmrn avatar b1rdex avatar bassim avatar bluemmb avatar brendt avatar chapeupreto avatar dependabot[bot] avatar erikgaal avatar freekmurze avatar github-actions[bot] avatar gummibeer avatar hackel avatar hergend avatar iamvar avatar jdecool avatar kevinreniers avatar milesizzo avatar msiemens avatar nelwhix avatar nielsvanpach avatar patinthehat avatar riwiki avatar sammousa avatar sebastiandedeyne avatar sk1ter avatar skyrpex avatar telkins avatar timacdonald avatar tomcoonen avatar xewl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

enum's Issues

Laravel 8 Issue: Too few arguments to function Spatie\Enum\Enum

I'm getting the following error:

In Enum.php line 53

Too few arguments to function Spatie\Enum\Enum::__construct(), 0 passed in app\Enums\BankAccountTypeEnum.php on line 11 and exactly 1 expected

app\Enums\BankAccountTypeEnum.php


namespace App\Enums;

use Spatie\Enum\Enum;

abstract class BankAccountTypeEnum extends Enum
{
    public static function current(): BankAccountTypeEnum
    {
        return new class() extends BankAccountTypeEnum
        {
            public function getIndex(): int
            {
                return 1;
            }

            public function getValue(): string
            {
                return 'Current / Cheque';
            }
        };
    }

    public static function savings(): BankAccountTypeEnum
    {
        return new class() extends BankAccountTypeEnum
        {
            public function getIndex(): int
            {
                return 2;
            }

            public function getValue(): string
            {
                return 'Savings';
            }
        };
    }

    public static function transmission(): BankAccountTypeEnum
    {
        return new class() extends BankAccountTypeEnum
        {
            public function getIndex(): int
            {
                return 3;
            }

            public function getValue(): string
            {
                return 'Transmission';
            }
        };
    }
}

config\custom\cdv.php

<?php

use App\Enums\BankAccountTypeEnum;

// config('custom.cdv.enabled')
return
[
    'enabled'       => env('CDV_ENABLED',false), // config('custom.cdv.enabled')
    'api'           => env('CDV_API'), // config('custom.cdv.api')
    'endpoint'      => env('CDV_ENDPOINT'), // config('custom.cdv.endpoint')
    'username'      => env('CDV_USERNAME'), // config('custom.cdv.username')
    'password'      => env('CDV_PASSWORD'), // config('custom.cdv.password')

    'curl_options'  => // config('custom.cdv.curl_options')
    [
        CURLOPT_CONNECTTIMEOUT  => 10,
        CURLOPT_TIMEOUT         => 30,
        CURLOPT_SSL_VERIFYHOST  => 2
    ],

    'test_account'  =>
    [
        'bank_bank'             => '',
        'bank_branch'           => '',
        'bank_branch_code'      => '',
        'bank_number'           => '',
        'bank_account_type'     => 'savings'
    ],
    
    // snapbill mapping
    'account_types'  => // config('custom.cdv.account_types')
    [
        BankAccountTypeEnum::current()->getIndex()       => 'current',
        BankAccountTypeEnum::savings()->getIndex()       => 'savings',
        BankAccountTypeEnum::transmission()->getIndex()  => 'transmission'
    ],
];

Enum serialized as empty array when using spatie/laravel-event-sourcing and spatie/data-transfer-object

When using enums inside a Spatie/DTO which is used for a StorableEvent, the Enum::jsonSerialize method never gets called and the enum gets saved as an empty array. (It might be a bug with the DTO or event sourcing package, but since you're working on those as well, I might as well report the bug where I see it :)

The data is correct until Event sourcing's JsonEventSerializer::serialize() call; Afterwards the enum gets replaced with an empty array.

(Heavily edited) files below:

Enum: ReportType.php
-----------------------------------------------------------
<?php

use Spatie\Enum\Enum;

/**
 * @method static self detailed()
 * @method static self generic()
 */
class ReportType extends Enum
{

//N.B. it doesn't matter whether custom values are specified or not, it still gets serialized as empty array
    protected static function values(): array
    {
        return [
            'detailed' => 0,
            'generic' => 1,
        ];
    }
}


Data Transfer Object: Report.php
----------------------------------------------------
<?php

use Spatie\DataTransferObject\DataTransferObject;

class Report extends DataTransferObject
{
    public ?ReportType $type = null;
}



Stored Event: ReportReceived.php
--------------------------------------------------
<?php
use Spatie\EventSourcing\StoredEvents\ShouldBeStored;

class ReportReceived extends ShouldBeStored
{
    public Report $report;

    public function __construct(Report $report)
    {
        $this->report = $report;
    }
}


Emitting the event
--------------------
<?php

$report = new Report(['type' => ReportType::from('detailed')]);
event(new ReportReceived($report));


Event Stored in database:
----------------------------------------------
79|||App\Context\Reports\Events\ReportReceived|{"report":{"type":[]}}|[]|2021-05-24 09:14:38

Unit test overridden values

Hello. Thanks for package, it's very useful until 8.1.
I've faced one issue with testing. I want to cover with unit tests overridden values.
I have an enum with few overridden values, since they can't be declared as method name because of dot in value:

<?php

declare(strict_types=1);

namespace Namespace\Type;

use Spatie\Enum\Enum;

/**
 * Value taken at start of interruption.
 * @method static self InterruptionBegin()
 *
 * Value taken when resuming after interruption.
 * @method static self InterruptionEnd()
 *
 * Value for any other situations.
 * @method static self Other()
 */
final class ReadingContext extends Enum
{
    /** @return array<string, string> */
    protected static function values(): array
    {
        return [
            'InterruptionBegin' => 'Interruption.Begin',
            'InterruptionEnd' => 'Interruption.End',
        ];
    }
}

And my test:

<?php

declare(strict_types=1);

namespace Namespace\Tests\Type;

use Namespace\Type\ReadingContext;
use Generator;
use PHPUnit\Framework\TestCase;
use Spatie\Enum\Phpunit\EnumAssertions;

use function PHPUnit\Framework\assertSame;

class ReadingContextTest extends TestCase
{
    /** @dataProvider dataProvider */
    public function testValues(ReadingContext $context, string $expected): void
    {
        EnumAssertions::assertIsEnum($context);
        EnumAssertions::assertSameEnumValue($context, $expected);
        assertSame($context->value, $expected);
    }

    /** @return Generator<int, array<int, mixed>> */
    public function dataProvider(): Generator
    {
        yield [ReadingContext::InterruptionBegin(), 'Interruption.Begin',];
        yield [ReadingContext::InterruptionEnd(), 'Interruption.End',];
    }
}

Tests passed fine, but I got in coverage result that ReadingContext::values() doesn't covered.
So my question is why not covered and maybe you could suggest how I can achieve that? PHP 8.0.8, PHPUnit 9.5.6
Thanks

Suggestion about compare Enum using Enum::isX():bool

First: Thanks a lot for your great work!

What do you think about Enum::isX() comparisons?

/**
 * @method static self success()
 * @method static self failure()
 * @method bool isSuccess()
 * @method bool isFailure()
 */
final class TheStatus extends Enum
{
}

I know that you can compare two values by equal comparison ==; On the other hand, identity comparisons === would require a singleton or main repository.

if ($status == TheStatus::success()) {
}

This is also possible and the recommended way?

if ($status->equals(TheStatus::success())) {
}

But I this looks better:

if ($status->isSuccess()) {
}

One solution is to implement this methods, one by one, without the magic that Enum provides.

Other solution could be capturing __call magic method.

abstract class EnumWithIsMethod extends Enum
{
    public function __call($name, $arguments)
    {
        if (strlen($name) > 2 && 0 === strpos($name, 'is')) {
            return $this->checkIs(substr($name, 2));
        }

        throw new \Error('Call to undefined method ' . __CLASS__ . '::' . $name . '()');
    }

    private function checkIs(string $key): bool
    {
        $key = $this->findKeyIgnoringCase($key) ?? $key;
        return $this->equals($key);
    }

    private function findKeyIgnoringCase(string $possibleKey): ?string
    {
        foreach ($this->getKeys() as $key) {
            if (0 === strcasecmp($possibleKey, $key)) {
                return $key;
            }
        }

        return null;
    }
}

I know that capturing __call is obtrusive and annoying... any suggestions?

Too few arguments to function Spatie\Enum\Enum::__construct(), 0 passed

Hi there!
Something strange is happening.
I'm using your package in my project with Laravel and Filament admin:

"laravel/framework": "9.19.0"
"php": "8.1.7"
"filament/filament": "2.13.21",
"spatie/enum": "3.13.0",

I have this model in App/Models/Lot

<?php

namespace App\Models;

use App\Enums\ProcedureType;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Lot extends Model
{
    use HasFactory, SoftDeletes;

    protected $casts = [
        'deadline' => 'datetime',
        'procedure_type_code' => ProcedureType::class,
    ];
}

And here's my Enum Class

<?php

namespace App\Enums;

use Spatie\Enum\Enum;
/**
 * @method static self not_specified()
 */

class ProcedureType extends Enum
{
    protected static function values(): array
    {
        return [
            'not_specified' => 0
       ];
    }

    protected static function labels(): array
    {
        return [
            'not_specified' => 'Not specified'
       ];
    }
}

In the db table the item has the correct value procedure_type_code = 0

But when i try to reach the listing page of admin filament for resource Lot, this error happens:

Too few arguments to function Spatie\Enum\Enum::__construct(), 0 passed in /var/www/html/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php on line 1589 and exactly 1 expected

Is that something I'm missing guys?
Thank you in advance

Index support for enum values

enumerable
adjective MATHEMATICS

able to be counted by one-to-one correspondence with the set of all positive integers.

This is the current definition of enumerable. The current Enum class does not implement this - it's only a string map. Instead it should be a map of strings to integers (positive).
So the current implementation should be changed at multiple points.

  • force the value to be integer
  • force the key to be string
  • force key and value to be unique

An idea would be to switch over to constants (key forced to be string and unique). While looping over all constants the value could be also checked for uniqueness.
The results of these reflection class operations could be cached in a static variable(s).

The definition of the class will also look more like an enum in this case.

/**
 * @method static self sunday()
 * @method static self monday()
 * @method static self tuesday()
 * @method static self wednesday()
 * @method static self thursday()
 * @method static self friday()
 * @method static self saturday()
 */
final class Days extends Enum
{
  const SUNDAY = 0;
  const MONDAY = 1;
  const TUESDAY = 2;
  const WEDNESDAY = 3;
  const THURSDAY = 4;
  const FRIDAY = 5;
  const SATURDAY = 6;
}

Constants like FOO_BAR could be converted to lowerCamelCase fooBar().

The usage could be Days::monday() to get a valued instance of the Enum. Days::from($day)->equals(Days::MONDAY) or without type safety Days::from($day) == Days::MONDAY.

Similar behavior is implemented in SplEnum http://php.net/manual/de/class.splenum.php without the other functionalities provided by this package.

Impossible to create custom static methods in extended class

I wanted to create a backwards compatible version of toArray() in the extended Enum class only to find out that all static methods in extending classes are considered to be "magical" and generate values inside enum.

Return value of Spatie\Enum\Enum::make() must implement interface Spatie\Enum\Enumerable, array returned

The problem is in the \Spatie\Enum\Enum::resolveFromStaticMethods method.

I think there should be some mechanism to prevent this. At least make this more obvious than generating the cryptic error message.

Method make

I was looking for a method like findByIndex or something like that. After looking at the code, I find out that the method make does not create a new enum, only gets a existing one by its index or name/value (at the docs its says that make: "Creating an enum from a value").
Am I the only one that find this a little confusing?

Enum::toArray() bc break? bug?

Enum::toArray() v1.0 used to return array as:

home => "home"
work => "work"
other => "other"

v2.0 returns

home => 0
work => 1
other => 2

Is this supposed to be like that? I find it counterintuitive. If this is a feature and not a bug it is definitely a bc break and should be noted in changelog

Anyway, thanks for this nifty library. Once we put it in production the postcard will definitely be on its way ;-)

Missing documentation for ->isXy()

While digging through the code I noticed that there's a feature to check for specific enum values:

/**
 * @method static self active()
 * @method static self passive()
 * @method bool isActive()
 * @method bool isPassive()
 */
class State extends Enum {
}

$state = State::active();
if ($state->isActive()) {
    echo "Is active\n";
}

However, there seems to be no documentation for this. If this is an officially supported feature, then it would be good to rectify this.

Personally, I think this is a superior way to check for specific values than the currently documented $state->equals(State::active());

initialize Enum with values coming from DB?

Could be I am asking a silly question.

I have valid codes stored in a database.
Would like to create an Enum using the values in the database.

So instead of:

/**
 * @method static self draft()
 * @method static self published()
 * @method static self archived()
 */
class StatusEnum extends Enum
{
}

Something like:

class StatusEnum extends Enum
{
    protected static function values(): array
    {
        // example attempt, getting code values from DB
        return StatusType::all()->pluck('code')->toArray(); // ['draft','published','archived']
    }
}

Expected end result for both is that it is possible to call StatusEnum::draft().

But my attempt results in an Enum without any values (because it seems to rely on the docblocks for definition):

StatusEnum::toValues() // []

Is there a way for the package to support this flow?

It might sound counterintuitive to have dynamic codes in the DB and have a hardcoded Enum (i.e. hardcoded StatusEnum::draft()), but this way I can deploy + code using default values and I don't have to redeploy if I ever have to add values. And: otherwise the Enum would be also be hardcoded: in the docblock.

Switch operator usage

Hello! Thanks for this library!

I have a question:
May I use this enums in switch operators? Have usage of this library with switch operator some pitfalls?

PHP8.1 Compatibility: check properties

We already use reflection so we might as well add a check to confirm that the enum subclasses don't have any (static) properties, as these are not allowed in PHP8.1 enums.

We can throw exceptions in this case, this would break BC. We could also trigger warnings using trigger_error, in theory this could be better but in practice many frameworks map these to exceptions anyway.

Add strict_types declaration?

As the library says that it is "strongly typed", wouldn't it make sense to add a declare(strict_types=1); to Enum::class?

Note: This should be possible without any problems as this library requires php >=7.2

Enums with Laravel Octane

Hello Spatie!

At first thank you for your awesome packages which literally makes our life way easier.

I'm using this package in laravel which is running over octane, but I have a problem where when I change the locale of the app, the enum labels which are translated strings using trans() stays on the first labels when the app first ran on octane, so apparently this issue is caused by the static loading of the enum values and labels.

I'll try to figure out a way to fix this, and if I did, I'll submit a PR to fix it.

Game over message on mate in 1

While playing on https://arkadiuszkondas.com/php-grandmaster/ I got a "game over" message when it was only mate in one.

image

e4 Nf6
e5 Ne4
Nc3 Nxc3
dxc3 f6
exf6 exf6
Bc4 g6
Nf3 c6
O-O b5
Qe2+ Be7
Bd3 Qa5
Bd2 Qa4
b3 Qg4
h3 Qh5
Rae1 Qc5
b4 Qd6
Bf4 Qe6
Nd4 Qxe2
Rxe2 Na6
Rfe1 Nxb4
Rxe7+ Kd8
cxb4 a5
a3 g5
Bd6 axb4
axb4 Ra3
Bf5 h5
R1e3 Ra2
Nb3 Ra3
Na5 Ra1+
Kh2 Ra4
Bg6 Rg8
Re8+ 

GetNames doesn't return the custom names

<?php

namespace App\Enums;

use Spatie\Enum\Enum;

class RegionEnum extends Enum
{
    public static function ea(): RegionEnum
    {
        return new class() extends RegionEnum {
            public function getName(): string
            {
                return 'East-Africa';
            }

            public function getIndex(): int
            {
                return 0;
            }
        };
    }

    public static function wa(): RegionEnum
    {
        return new class() extends RegionEnum {
            public function getName(): string
            {
                return 'West-Africa';
            }

            public function getIndex(): int
            {
                return 1;
            }
        };
    }

    public static function sa(): RegionEnum
    {
        return new class() extends RegionEnum {
            public function getName(): string
            {
                return 'South-Africa';
            }

            public function getIndex(): int
            {
                return 2;
            }
        };
    }

    public static function global(): RegionEnum
    {
        return new class() extends RegionEnum {
            public function getName(): string
            {
                return 'Global';
            }

            public function getIndex(): int
            {
                return 3;
            }
        };
    }
}

Calling RegionEnum::getNames(); returns the name of the functions instead of the name.

array:4 [β–Ό
  0 => "EA"
  1 => "WA"
  2 => "SA"
  3 => "GLOBAL"
]

Creating enum using method call from within enum throws UnknownEnumMethod

After upgrading to v3, I ran into the following issue (this used to work in v2):

Given this enum:

/**
 * @method static self open()
 * @method static self cancelled()
 * @method static self closed()
 */
class SomeStatus extends Enum
{
    public function isFinal()
    {
        return $this->equals(SomeStatus::cancelled(), SomeStatus::closed());
    }
}

When calling:

SomeStatus::open()->isFinal();

An exception is thrown: Spatie\Enum\Exceptions\UnknownEnumMethod
with Method cancelled not found on class SomeStatus.

Support standard strict comparison

Currently each instantiation of an enum creates a new object, this makes comparing instances hard.

Since we already have a static constructor we could consider improving the DX:

  1. Make the constructor private & final
  2. Instead of creating a new object on every call to make() we only create 1 per value.

This will allow normal strict comparisons to work: $enum1 === $enum2.

Note that the normal advantage of the flyweight pattern, reduced memory usage, is not really relevant here this is more about DX with respect to using normal comparison operators.

I think this also aligns with how 8.1 will implement enums.

Using an Enum as a Class Property or Method Parameter

Is it possible to use an Enum as a class property and assign a default value? It seems like I cannot initialize a member/field of my classes using the Enums I've created.

Should one of these two approaches work?
The first doesn't result in an error, but does not appear to function property;
The second gives the error Constant expression contains invalid operations

   /**
    * @var MonthEnum $month
    */
   private $month = MonthEnum::january;
   /**
    * @var MonthEnum $month
    */
   private $month = MonthEnum::january();

I also have this same issue if I try to set a default value for a method/function parameter.
Surely there's a way to do this without having to explicitly assign the default in a constructor or method body - when no parameter value is provided?

Method to retrieve a list of possible values

I often find myself needing a list of acceptable enum values, such as to build a 'where in' database query, for example. It would be convenient if I could just call MyEnum::values() and get a list of values instead of having to use array_keys(MyEnum::toArray()) every time.

The implementation is simple, but I decided to open an issue as I'm not sure if this would be seen as necessary.

    public static function __callStatic(string $name, array $arguments)
    {
        if ($name === 'values') {
            return array_keys(static::toArray());
        }

        return new static($name);
    }

I'm also not sure if this should use array_keys or array_flipβ€”it might be useful to have the labels in some cases. It also works fine when implementing a protected values method.

Deprecate values

It would be great to annotate deprecated values to indicate values that should not be used anymore. Is there already a possibility for this?

Consider using generics for better IDE support

Hi Spatie Team,
Love the package!

I've noticed in the docblock for the Enum class, there's the following line @property-read string|int $value
Whilst the type string|int is correct, it feels like a slight improvement could be made by using something like the following

/**
* @template T of string|int
* @property-read T $value
* /

Since that would allow users to use @extends Enum<int> to declare that enum is backed by an int (as opposed to the possibility that it's a string)

I believe this is BC due to the constraints laid out here: https://github.com/spatie/enum/blob/master/src/Enum.php#L87

Would love to write the patch myself, providing there's a likelihood of acceptance
Many Thanks!

I cant migrate my table with the foreign key

2 PDOException::("SQLSTATE[HY000]: General error: 1005 Can't create table caddo.project_translations (errno: 150 "Foreign key constraint is incorrectly formed")")
C:\xampp\htdocs\caddotrading\vendor\doctrine\dbal\lib\Doctrine\DBAL\Driver\PDOStatement.php:117

   Schema::create('project_translations', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->unsignedInteger('project_id');
            $table->string('name');
            $table->text('description');
            $table->string('locale')->index();
            $table->unique(['project_id','locale']);
            $table->foreign('project_id')->references('id')->on('projects')->onDelete('cascade');
            $table->timestamps();
        });

OPcache strip comments problem

Hi,

Usually, on production envs OPcache is set to strip comments from the processed files. That breaks this package because it relies on the phpdoc blocks for enum values. In order to prevent this you must explicitly tell PHP not to do that with following directive in php.ini:

opcache.save_comments=1

Please take note that you don't always have access to php.ini on production servers and the idea itself to rely on phpdoc bloks doesn't sound very stable if you ask me.

Could we think in a way that we refactor this to a more stable approach, especialy now with native enums in php8.1+.

Thanks!

The given value is not available in this enum

Hi!

Is the intended behaviour to throw an Exception whille calling enum statically with wrong value?

Approver::isDirector('wrong name')

Is there any other way to use the short type of call instead of doing

Approver::director()->isEqual($employee->getRole())

Thanks

Public function is detected as a static function

Hey there!

I have a enum defined like this:

/**
 * @method static self category()
 * @method static self cmsPage()
 * @method static self product()
 */
class ObjectType extends Enum
{
    public function createModel()
    {
        // ...
    }
}

When creating a new instance with ObjectType::make, I get the following error:

ErrorException: forward_static_call() expects parameter 1 to be a valid callback, non-static method ObjectType::createModel() should not be called statically
.../vendor/spatie/enum/src/Enum.php:180
.../vendor/spatie/enum/src/Enum.php:239
.../vendor/spatie/enum/src/Enum.php:113
.../vendor/spatie/enum/src/Enum.php:203
.../vendor/spatie/enum/src/Enum.php:332
.../vendor/spatie/enum/src/Enum.php:176

From debugging this a little I can see that createModel is detected as a static method in

enum/src/Enum.php

Lines 291 to 300 in 93154c3

foreach ($reflection->getMethods(ReflectionMethod::IS_STATIC | ReflectionMethod::IS_PUBLIC) as $method) {
if (
$method->getDeclaringClass()->getName() === self::class
|| in_array($method->getName(), $selfMethods)
) {
continue;
}
$values[] = $method->getName();
}

The reason seems to be that getMethods' filter parameter is equivalent to an OR test:

Any bitwise disjunction of ReflectionMethod::IS_STATIC, ReflectionMethod::IS_PUBLIC, ReflectionMethod::IS_PROTECTED, ReflectionMethod::IS_PRIVATE, ReflectionMethod::IS_ABSTRACT, ReflectionMethod::IS_FINAL, so that all methods with any of the given attributes will be returned.
(https://www.php.net/manual/en/reflectionclass.getmethods.php)

So it seems like it matches all methods that are either public or static.

Extend already extended

Hi guys,

I have a very specific case. Where I need to extend enum but keep the properties. See below:

<?php

/**
 * @method static self ANY()
 */

use Shipments\Enums\ShipmentStatusEnum;

class ShipmentStatusWithAnyEnum extends ShipmentStatusEnum
{
    protected static function values(): array
    {
        $out = parent::values();
        $out['ANY'] = 'any';
        return $out;
    }
}

<?php

declare(strict_types=1);

namespace Shipments\Enums;

use Data\Enums\EnumBase;

/**
 * @method static self DELIVERED()
 * @method static self PICKUP()
 * @method static self IN_TRANSIT()
 * @method static self OUT_FOR_DELIVERY()
 * @method static self FAILED_ATTEMPT()
 * @method static self EXCEPTION()
 * @method static self EXPIRED()
 * @method static self WAITING_DELIVERY()
 * @method static self PENDING()
 */
class ShipmentStatusEnum extends EnumBase
{
    public static function isFinal(self $enum): bool
    {
        return $enum->equals(self::DELIVERED(), self::PICKUP());
    }

    protected static function values(): array
    {
        return [
            'DELIVERED' => 'delivered',
            'PICKUP' => 'pickup',
            'IN_TRANSIT' => 'in_transit',
            'OUT_FOR_DELIVERY' => 'out_for_delivery',
            'FAILED_ATTEMPT' => 'failed_attempt',
            'EXCEPTION' => 'exception',
            'EXPIRED' => 'expired',
            'WAITING_DELIVERY' => 'waiting_for_delivery',
            'PENDING' => 'pending',
        ];
    }
}

I saw why this is not possible, because of:

private static function resolveDefinition(): array

I can fix, to pick all @method static self till the current abstract class, but I was wondering if you would consider accepting the pull request.

Forgot to drop the EnumBase

<?php

declare(strict_types=1);

namespace Data\Enums;

use Spatie\Enum\Enum;

abstract class EnumBase extends Enum
{
    /**
     * Check if value exist based on availbale.
     * @param int|string $value
     * @return bool
     */
    public static function exist($value): bool
    {
        return in_array($value, static::values());
    }

    /**
     * @return static[]
     */
    public static function all(): array
    {
        $out = [];
        foreach (self::values() as $value) {
            $out[] = self::make($value);
        }

        return $out;
    }
}

Strong typed enums problems

I use laravel json API. I pass the json payload to the BE.
I have a X enum and changed the values to integers - to keep the integers in DB for better indexing.

When I pass 1 (integer) it passes the validation and record being created.
When I pass "1" (string) it passes the validation but in creation flow I 500 error:
There's no value 1 defined for enum App\Enums\ENUM, consider adding it in the docblock definition

This is validator:
'enum_type' => ['required', 'integer', 'in:' . implode(',', Enum::toValues())],

How to solve this? How to "tell" to the class that the digit can be both of string and/or integer types?

Enum class:
protected static function values(): array { return [ 'enum1' => 1, 'enum2' => 2, ]; }

Setting Enum values by `static::$map` leads to issues on creating instances and equality

As mentioned in README.md:

/**
 * @method static self draft()
 * @method static self published()
 * @method static self archived()
 */
class StatusEnum extends Enum
{
    protected static $map = [
        'draft' => 1,
        'published' => 'other published value',
        'archived' => -10,
    ];
}

A enum instance can be created using its identifier

$status = new StatusEnum('draft');

Or its value

// by value, oops!
$status = new StatusEnum(1);

The last attempt will result on \TypeError since __constructor expect a string as first argument.

Casting the value to string

$status = new StatusEnum('1');
// oops, it is not equal now
$status->equals(StatusEnum::draft());

The last attempt created the object and set it to a value of string 1. So when comparing to a recent created instance fails to compare to int 1.

Focus points

When constructing the object, using in_array() without strict comparison leads to an item found but it will not be able to compare against itself because stored value is a string and not the override-by-map value. The possible errors are several when using objects or string to integer comparisons.

It can be recognized as equals but then calling equals they won't.

When constructing the array of possible values on Enum::resolve().
So, in the case of StatusEnum class the result will be

    [
        'draft' => 1, // integer
        'published' => 'other published value',
        'archived' => -10,
    ]

A possible solution if the intention is to have possible values for construct object as strings (as signatures are __construct(string): self and from(string): Enum). Could be to use map values as strings also:

// from
$enumValues[strtolower($methodName)] = static::$map[$methodName] ?? $methodName;
$enumValues[strtolower($valueName)] = static::$map[$valueName] ?? $valueName;
// to
$enumValues[strtolower($methodName)] = strval(static::$map[$methodName] ?? $methodName);
$enumValues[strtolower($valueName)] = strval(static::$map[$valueName] ?? $valueName);

If the intention is to have values of any type, then it would need to drop type definition of string in __construct, from and also do weak comparisons on equals.

Is the intention to have values of any type configured in static::$map or those values should be string?
If true then signatures for __construct and from should not be restricted to string.
If false then ensure that resolve() returns array<string, string>.

Is the intention to compare values as equality == or identity ===?
If true then fix equals method.
If false then fix in_array inside __construct.

What are your thoughts?

Feature regression with values and labels

Hi,

With the v2, I had access to static methods getNames and getValues.

In the v3, I understand they are now accessible with labels and values but these are now protected and doesn't return default labels & values.

For example, it was very helpful to automatically build a select from an enum.

I don't know if this was intended for this new major version. Would it be acceptable to restore this behavior ?

Regards

This code doesn't follow our standards

Following the same habits and standards across our packages ensure that everybody at Spatie and all contributors have an enjoyable time working on the code. When looking at the code I've noticed that it doesn't follow our standards or way of doing things on several points.

Map by name=>value

Hi, is there any way to get something like below:

class SaveStatus extends Enum
{
    public static function toArray(): array
    {
        return array_combine(static::getNames(), static::getValues());
    }
}

I use names instead of indexes so the mapping should be name=>value

Localised labels aren't update after locale is changed

Assuming SomeEnum is an enum which protected static function labels(): array result varies according to the locale. Labels are cached and never updated, even after changing the locale.

App::setLocale('de_DE');

dump(SomeEnum::foo()->label); // expected: german, actual: german

App::setLocale('fr_FR');

dump(SomeEnum::foo()->label); // expected: french, actual: german

PHPUnit assertions trait changed to class

It seems that this commit changed the trait that provided PHPUnit assertions into an abstract class: 45245a1

I've got a project on PHP 7.4 with spatie/enum v3.10.0. I ran a composer update this morning and was given v3.11.1. I'm now getting a fatal error when running my tests:

PHP Fatal error:  Tests\TestCase cannot use Spatie\Enum\Phpunit\EnumAssertions - it is not a trait in /home/runner/work/example.com/example.com/tests/TestCase.php on line 19

I can obviously refactor my tests to use the static methods on what is now the class - but should this not have been documented as a breaking change and a new major version released?

Get a random value for seeds

Hey Spatie team !

First me and my team definitely love your job (a postcard is ready to be sent ^^)

What do you think about a MyEnum::rand() method to use it into factories / seeds ?
Is it useless or it could be interesting to open a PR ?

Thanks !

Future of this package after PHP 8.1

Now that the native PHP enums RFC is accepted we have to think about the future of this package.

https://wiki.php.net/rfc/enumerations#voting

Following the RFC I think that there's nothing this package provides on top except of labels.
And some static methods like the toArray() toValues() ...

The Laravel wrapper package has some more features we should keep. So a custom interface including abstract class with our on-top logic could be nice.

Question is if we want this as a dedicated base package or move it into the Laravel wrapper?

Start index from one and custom logic

Hi, I'm using spatie/enum by one day and I want to create enum with values that have index from 1 to N instead of 0 to N - 1.
I've created a class that extends Enum class and override the resolve method, adding + 1 .

Code:

protected static function resolve(): array
{

   // Code ...

   foreach ($values as $index => $value) {
        $name = strtoupper($value);

        self::$cache[$class][$name] = [
            'name' => $name,
            'index' => static::getMappedIndex($name) ?? $index + 1,
            'value' => static::getMappedValue($name) ?? $value,
        ];
    }

    // Code ...
}

Is the right way or is there another way to do this? (without use MAP_INDEX)
If no, why not to create a new property or function to use inner the foreach?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.