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 Concern Laravel Package

emreyarligan/enum-concern

Adds a lightweight EnumConcern trait for PHP/Laravel enums with helpers for labels, values, options, validation rules, and select arrays, making enums easier to use in forms, APIs, and Blade views.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and publish (2 minutes)

composer require <vendor>/<package>
php artisan vendor:publish --tag=<package>-config   # only if you need to override defaults

There is no migration or service-provider registration step for the core feature; the package auto-discovers.

Where to look first

  1. config/<package>.php — matching strategy, fallback behaviour, and cache settings live here. Read this before writing any call site.
  2. The class exposing fromValue() — this is the single entry point you will use 90% of the time. Skim its docblock for the accepted input types (scalar, null, arrays) and the exception it throws on a miss.
  3. The 1.0.7 release note itself: fromValue now accepts custom method-based matching, meaning a lookup no longer has to be a strict identity comparison against a stored value.

First use case

Resolve a raw value coming out of the database or an API payload into a domain object, instead of hand-rolling match/switch statements at every call site:

// Before: scattered, easy to let drift
$status = match ($row->status) {
    'paid', 'captured' => PaymentStatus::Paid(),
    'void'             => PaymentStatus::Void(),
    default            => throw new InvalidArgumentException("Unknown status [{$row->status}]"),
};

// After
$status = PaymentStatus::fromValue($row->status);

Start by converting one high-traffic resolution path (a model cast or a single controller). Confirm the returned object is what you expect before rolling it across the codebase — the failure mode of a wrong match is silent misclassification, not an exception.

Implementation Patterns

Cast it, don't call it manually

The highest-leverage integration is a custom cast, so resolution happens once at the model boundary and every downstream consumer gets a typed object:

protected $casts = [
    'status' => PaymentStatusCast::class,
];

// in the cast
public function get($model, $key, $value, $attributes)
{
    return $value === null ? null : PaymentStatus::fromValue($value);
}

This removes fromValue from your controllers entirely and keeps the matching rules in one place when the custom-matching feature later needs tweaking.

Register custom matchers in a service provider, not inline

With 1.0.7 the resolution logic can be a method rather than a literal comparison. Keep those methods in one place — a dedicated MatchersServiceProvider (or the package's config array if it exposes one) — so the precedence order is visible in a single file. Day-to-day, this reads well when legacy data has aliases:

// config: 'matchers' => [LegacyAliasMatcher::class],
PaymentStatus::fromValue('CAPTURED');   // resolves via matcher, not identity
PaymentStatus::fromValue('captured');   // still resolves via default identity path

Validation rule pairing

If a request field feeds a fromValue call, add a Rule::enum(...) (or the package's own rule, if shipped) to the FormRequest. fromValue throwing inside a controller produces a 500; a validation rule produces a 422 with a useful message. Match the rule's accepted set to the matcher's accepted set — this is the single most common source of "it validates but still blows up".

API resources and serialisation

Resolve on the way in, not on the way out. Serialise the matched object's canonical value (or ->value/->key) in your JsonResource, so a matcher that accepts three legacy aliases still emits one stable string to clients. This keeps the custom matching an internal concern and stops alias proliferation in your public contract.

Bulk paths

For imports and seeders, prefer chunked resolution with a per-chunk try/catch that collects failures into a report, rather than letting the first bad row abort a 50k-row import. In factories, use the canonical value — writing aliases in test fixtures hides regressions in the default (non-matcher) path.

Testing the new matcher path

Write one test per matcher asserting both a hit and a miss, and one test asserting that with no matchers configured the behaviour is identical to 1.0.6. That last test is your upgrade guard rail.

Gotchas and Tips

Matcher precedence is order-sensitive. When several custom matchers could claim the same input ('paid' vs 'PAID' vs 'paid '), the first match wins. If you need deterministic behaviour, sort matchers from most specific to most general and document that order — don't rely on registration order in a service provider you might later split.

Ambiguity is silent. Unlike a fromValue miss, which throws, an over-eager matcher (a str_contains or a case-insensitive comparison) will happily return the wrong case. Favour explicit maps over fuzzy matching; if you must truncate/trim, do it in one named matcher so it is greppable.

Null and empty string are different inputs. Confirm whether fromValue(null) throws, returns null, or resolves to a default. If your cast handles null before delegating, keep that guard — otherwise a nullable column becomes a runtime exception on the first legacy row.

Strict comparison still applies on the default path. 1 and '1' are not interchangeable unless a matcher normalises them. This bites hardest when values come from PDO (often strings) versus from a JSON payload (often ints). Cast at the boundary.

Cache invalidation. If the package memoises resolved values or matcher results, a config/opcache change in a long-running worker (Octane, queue workers) will not be picked up until restart. After deploying new matchers, php artisan queue:restart and reload Octane.

Exception type matters. Catch the package's specific exception (InvalidArgumentException subclass), never \Throwable, and convert it to a domain-level error or a validation failure. Log the offending raw value — it is the only thing you need to add the missing matcher.

Extension points worth knowing. The matcher interface (implement it for custom logic), the config array for registration, and the cast for boundary normalisation. Anything beyond that — subclassing the resolver — is usually a sign the matching rules should live in your own domain enum instead.

Upgrade note for 1.0.7. This is a patch-level release adding opt-in capability; the default fromValue path is unchanged, so no code changes are required. Treat adoption as a separate, deliberate change: add matchers, add the precedence tests, then upgrade. If you are on a 0.x line, check the changelog for the cast/resolver signature separately — that jump is where breakage would live.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource