Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Enum Bundle Laravel Package

yokai/enum-bundle

Yokai Enum Bundle adds practical PHP 8.1 enum support: register enum types, expose choices in forms, validate values, and convert between enums, scalars, and database values through configurable services.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install

composer require yokai/enum-bundle

The bundle is a thin Symfony integration layer over yokai/enum (the actual Enum base class lives in the companion package and will be pulled in as a dependency). There is no configuration tree you must fill in — it works out of the box after registration.

2. Register the bundle

// config/bundles.php
return [
    // ...
    Yokai\EnumBundle\YokaiEnumBundle::class => ['all' => true],
];

With Symfony Flex this is the only step; without Flex you'll also need to check config/routes.php only if you intend to use any shipped controller (the bundle has none by default).

3. Look here first

  • vendor/yokai/enum/src/ — the Enum base class and its getValues() / getChoices() / label resolution logic. This is the real API surface.
  • vendor/yokai/enum-bundle/src/DependencyInjection/ — the compiler pass that auto-tags your enum classes. Everything the bundle "gives you" is a consequence of this tagging.
  • vendor/yokai/enum-bundle/src/Form/ — the EnumType form type, which is the highest-value thing you get for day-to-day work.

4. First use case: a form select backed by an enum

// src/Enum/OrderStatus.php
namespace App\Enum;

use Yokai\Enum\Enum;

final class OrderStatus extends Enum
{
    public const PENDING   = 'pending';
    public const PAID      = 'paid';
    public const SHIPPED   = 'shipped';
    public const CANCELLED = 'cancelled';
}
// src/Form/OrderType.php
use App\Enum\OrderStatus;
use Yokai\EnumBundle\Form\Type\EnumType; // verify namespace in README for your version

$builder->add('status', EnumType::class, [
    'class' => OrderStatus::class,
]);

The form type reflects over the constants and gives you choices keyed by the constant value. That is the 80% use case: one class defines the allowed values, and the form, validator, and Twig all read from it instead of a drifting array literal.

Implementation Patterns

Single source of truth for choice lists. The main win over a raw array is that the enum class becomes the contract. Controllers, fixtures, and tests all reference OrderStatus::PAID instead of the string 'paid', so a rename is a single-file change plus a static analysis pass.

Enum type on forms, everywhere. Once EnumType is registered you can use it on every entity that has a status/category/kind column:

$builder
    ->add('status', EnumType::class, ['class' => OrderStatus::class])
    ->add('priority', EnumType::class, [
        'class'    => Priority::class,
        'required' => false,
        'placeholder' => 'Any',
    ]);

Pass the class via the class option — there is no autowiring for this, the form type is generic by design so one type serves all enums.

Labels via translation. The bundle's value over a plain array is label resolution: the enum exposes human-readable labels, typically through the translator, so OrderStatus::PAID renders as "Paid" and is translatable per-locale. Put the translation keys in a dedicated domain (e.g. enums.en.yaml) so they don't collide with your form/validator translations:

# translations/enums.en.yaml
order_status:
    pending: Pending
    paid: Paid

Twig rendering. Rather than {{ order.status }}, use the bundle's Twig helper/filter so labels stay consistent with the form:

{{ order.status|enum_label('App\\Enum\\OrderStatus') }}

Confirm the exact filter name in the README — this is the one API that has churned between versions.

Doctrine storage. The idiomatic approach is a plain string/varchar column holding the constant value, with the enum enforced at the application boundary (form type + validator), not at the DB layer. That keeps migrations boring:

#[ORM\Column(type: 'string', length: 32)]
private string $status = OrderStatus::PENDING;

public function setStatus(OrderStatus|string $status): self { /* ... */ }

If you want DB-level safety, add a CHECK constraint in a migration rather than fighting a custom DBAL type.

Validation outside forms. Anywhere you accept a status from an API payload, validate with an in constraint sourced from the enum, not a hardcoded list:

new Assert\Choice(choices: OrderStatus::getValues())

This keeps API validation and form validation in lockstep with zero extra wiring.

Extension point: custom enums. Because the bundle discovers enums by tag/autoconfiguration, if you keep enums outside the default src/ namespace (e.g. in a shared lib/ package), you'll need to register the tag manually:

services:
    App\Shared\Enum\:
        resource: '../lib/Enum/'
        tags: ['yokai_enum.enum']   # verify exact tag name in the bundle

Testing. Enums are pure static reflection, so they need no container in tests:

self::assertContains($order->getStatus(), OrderStatus::getValues());

For form tests, pass the class option and assert against OrderStatus::getChoices() so the assertions don't duplicate the label list.

Gotchas and Tips

These are not PHP 8.1 native enums. Yokai\Enum\Enum predates them and is a constants-based abstraction. You cannot pass it where a \BackedEnum is expected — no ->value, no tryFrom(), no match exhaustiveness. If your codebase is PHP 8.1+, think hard before adding this: native enum plus a small label helper gets you most of the benefit with first-class language support and IDE refactoring. This bundle earns its keep mostly in older codebases or where the translator-backed label/twig integration saves real time.

Constant values must be scalar. getValues() walks class constants via reflection; array or object constants will break choice generation. Keep every constant a string or int, and keep them unique — duplicates silently collapse in the choices array.

Container is compiled: new enums need a cache warmup. Because enums are discovered at compile time, adding a new enum class in dev usually works (auto-reload), but in a preloaded/prod setup a stale container means the form type won't know about it. bin/php-cache-clear (or cache:clear) is the first thing to try when a new enum "doesn't show up".

Debugging discovery. When an enum isn't registered, ask the container rather than guessing:

bin/console debug:container --tag=yokai_enum.enum
bin/console debug:container EnumType

If the tag list is empty, it's an autoconfiguration/resource-path problem, not an enum problem.

Translation misses look like blank options. If a label key is missing, you get the raw key or the raw value in the select — easy to mistake for a form-type bug. Check the translation domain configured for the enum before digging into the bundle.

Form type needs class, always. There's no way to infer the enum from the property type (the property is usually a string), so forgetting 'class' => … produces a generic "option required" error. Wrap it in a small app-level OrderStatusType if you use the same enum in many forms.

Don't let the enum leak into entities' signatures if you may swap it out. Since the enum isn't a native type, entities usually store string. That's actually a feature: removing the bundle later is a mechanical change (replace OrderStatus::X with a native enum or constants class) because nothing is persisted as a serialized enum object.

Vendor risk. 12 stars, zero dependents, single-maintainer ecosystem (Yokai is a French consultancy; the package is dogfooded in their projects, which is why it's solid but niche). The last release is recent, so it's maintained — but treat it as a small, stable utility rather than a load-bearing framework. Keep it behind your own thin form-type/Twig wrapper so a future replacement is a two-file change, not a grep across the codebase. Pin the major version in composer.json and read the changelog before minor bumps; the project has historically been willing to rename public APIs between majors.

License is fine. MIT, no constraints on commercial use or redistribution.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib