spatie/enum: object enums for PHP, now superseded by PHP 8.1
Strongly typed enums in PHP supporting autocompletion and refactoring
At a glance
- What is it?
- spatie/enum gave PHP developers strongly typed enums as objects, with IDE autocompletion and refactoring, before PHP 8.1 shipped native enums, and its own README now marks the package obsolete and points new projects at the language feature instead.
- Who is it for?
- spatie/enum belongs in an existing PHP codebase that is not yet on PHP 8.1 and wants typed enum objects with IDE support today, and it belongs nowhere new, because the README itself recommends native enums for new projects. It is also the wrong choice for a team that wants the exhaustive match semantics that a language level enum gives you, since this package offers objects and static analysis rather than compiler checked exhaustiveness.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The README calls the package obsolete
The most important line in the README is a warning at the top. With the introduction of Enums in PHP 8.1, this package is now considered obsolete. For new projects, the recommendation is to use native enums instead of this package, with a link to an article on native enums.
That changes how to read everything else. This is not a library to evaluate for a greenfield project, because the language has moved past it. It is a library you might adopt in an application that cannot yet move to PHP 8.1, or one you might find in a dependency graph and need to understand.
The release history is consistent with that reading. The tagged versions are 3.13.0 from 2022-04-22, 3.12.0 from 2022-02-05 and 3.11.1 from 2021-11-25, and the documentation home page is the version 3 documentation. The repository is not archived and the last push was on 2026-08-07, so it is not dead, but it is a package in maintenance rather than development mode.
Spatie is a web design agency and the package carries the standard MIT licence with a LICENSE.md file at the root, alongside a CHANGELOG.md, a psalm.xml.dist for static analysis configuration, a phpunit.xml.dist for tests and an UPGRADING.md that is worth opening if you are considering it.
Enums are objects, never bare constant values
The design decision that defines this package is stated plainly: enums are always objects, never constant values on their own. The README gives the reason, which is that this allows for proper static analysis and refactoring in IDEs.
The API is built on static factory methods declared in docblocks. A class extends Enum and declares its cases with @method annotations:
use \Spatie\Enum\Enum;
/**
* @method static self draft()
* @method static self published()
* @method static self archived()
*/
class StatusEnum extends Enum
{
}And you use them through those methods:
$class->setStatus(StatusEnum::draft());The point of the object form is that a value carries behaviour and type information. A method signature that takes a StatusEnum cannot be handed a loose string, and a refactoring tool can rename a case and update the call sites, which is what the README's autocompletion and refactoring screenshots demonstrate.
The cost is that the cases are not enforced by the language. The @method annotations are documentation that an IDE reads, and if your static analysis setup is not reading them, an unknown case is not a compile error. That is the fundamental gap between this package and a native enum, and it is the reason the two are not interchangeable.
from() throws, tryFrom() returns null
Constructing an enum from a stored value is the case the package is explicit about. The README says the only time you want to construct an enum from a value is when unserializing them from, for example, a database.
There are two ways to do it:
$status = StatusEnum::from('draft');When the enum value does not exist, from() throws a BadMethodCallException. If you would rather not catch an exception, there is an alternative:
$status = StatusEnum::tryFrom('draft');With tryFrom(), a missing value gives you null instead of an exception.
The same contract exists on the read side. The value is exposed through a read-only property:
$status->value;and the README notes that value is read-only and cannot be changed. That immutability is the point of the design. An enum instance represents one case, and nothing in your code can turn a published case into a draft one after construction.
Overriding values and labels with a closure when the rule is uniform
By default the enum value is its method name. You can override that with a values() method, which is how you store enums as integers in a database instead of as method names:
class StatusEnum extends Enum
{
protected static function values(): array
{
return [
'draft' => 1,
'published' => 2,
'archived' => 3,
];
}
}Two details are documented and easy to get wrong. An enum value does not have to be a string, so it can be an int as in the example. And you do not need to override all values, only the ones that differ from the default.
When the rule applies to every case rather than to selected ones, the method may return a Closure instead of an array:
class StatusEnum extends Enum
{
protected static function values(): Closure
{
return function(string $name): string|int {
return mb_strtolower($name);
};
}
}Labels follow the same pattern. Overriding the labels method gives an enum a human readable label, and the default label is the enum's value:
class StatusEnum extends Enum
{
protected static function labels(): array
{
return [
'draft' => 'my draft label',
];
}
}Labels are also readable through a read-only label property, and labels may also be produced by a Closure when the transformation is uniform. The read-only treatment of both value and label is consistent throughout, which is the strongest guarantee the package offers.
Comparison, test assertions and a Faker provider
Equality has its own method, equals(), and it accepts several enums at once, returning true if the current enum equals one of them:
$status->equals(StatusEnum::draft(), StatusEnum::archived());The testing story is handled by an abstract class, Spatie\Enum\Phpunit\EnumAssertions, with a set of basic assertions for 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::assertEqualsEnum(StatusEnum::draft(), 'draft'); // checks if actual (transformed to enum) equals expectedThe set distinguishes being an enum at all, being a valid value of a given enum, being a valid label, and being equal or the same after transformation. That granularity is useful when a test asserts on data that came out of a request rather than from your own code.
For data generation there is a Faker provider, Spatie\Enum\Faker\FakerEnumProvider, which exists because generating random enum values with default faker would otherwise be a lot of copy and paste. If your test suite seeds fixtures with random statuses, that provider removes the boilerplate.
The package also ships an UPGRADING.md and a CHANGELOG.md at the root, which is the first place to look if you inherit this dependency and need to know which major version you are on.
What the package cannot do for you
The honest limitation is the same one the README states: PHP 8.1 native enums do the same job inside the language, and they do it with guarantees this package cannot match.
A native enum is checked by the engine. It is a closed set of cases that the compiler knows about, which means a match statement over an enum can be proven exhaustive, and a case that you add to the enum and forget to handle in a switch becomes a visible problem rather than a silent default branch. This package offers none of that. The case list lives in a docblock, static analysis tools read it if they are configured to, and a case that exists in a database but not in the docblock simply throws or returns null at runtime.
The second limitation is version reach. If your application must run on PHP 7.x or on a PHP 8.0 host, native enums are not available and this package still solves the immediate problem of typed constants with autocompletion. That is the one scenario where it remains the right call, and the README's warning is about new projects rather than about the package being broken.
The third is maintenance. With a last push on 2026-08-07 and tagged releases from 2022, a team adopting it today is adopting something that receives fixes but not new capability, and that will need a migration later rather than sooner. The installation step is deliberately small:
composer require spatie/enumso the cost of trying it is low. The cost of staying on it is the eventual migration to native enums, and the UPGRADING.md file in the repository is the first document to read when you plan that.
Editorial conclusion
spatie/enum belongs in an existing PHP codebase that is not yet on PHP 8.1 and wants typed enum objects with IDE support today, and it belongs nowhere new, because the README itself recommends native enums for new projects. It is also the wrong choice for a team that wants the exhaustive match semantics that a language level enum gives you, since this package offers objects and static analysis rather than compiler checked exhaustiveness. Before adopting it, check the PHP version your application must support, and if that constraint holds, read UPGRADING.md in the repository, since the migration path off the package is documented there.
Frequently asked questions
Is spatie/enum still worth using?
The README states that with the introduction of Enums in PHP 8.1 the package is now considered obsolete, and it recommends native enums for new projects. It remains usable for codebases that cannot yet move to PHP 8.1, and the repository is not archived, with the last push on 2026-08-07.
How do I install spatie/enum and define an enum?
Install it with composer require spatie/enum, then extend Spatie\Enum\Enum and declare the cases as @method static self annotations in the class docblock, for example @method static self draft(). You then use a case through the static method, such as StatusEnum::draft().
How do I create an enum from a stored value in spatie/enum?
StatusEnum::from('draft') throws a BadMethodCallException when the value does not exist, while StatusEnum::tryFrom('draft') returns null instead. The README notes the only time you want to construct an enum from a value is when unserializing them, for example from a database.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spatie-enum)Was this useful?