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

Enumerator Laravel Package

laranail/enumerator

View on GitHub
Deep Wiki
Context7
0.1.0

Removed

  • The generic command aliases. make:enumerator, enumerator:cache, enumerator:cache:clear, enumerator:annotate, enumerator:export and enumerator:ide-helper are gone; the laranail::enumerator.* names they shadowed are the only ones now.

    Artisan's registry is a flat map, so those aliases handed back exactly the collision the namespaced names exist to prevent — and made the convention decorative, since the short name is the one anybody would actually type. make:enumerator was the worst of them: it colonised Laravel's own make: namespace, where a future framework generator or another package would simply have replaced it.

Changed

  • The config key is laranail.enumerator, published to config/laranail/enumerator.php. Every read moves with it — config('enumerator.cache.driver') is now config('laranail.enumerator.cache.driver'). Laravel's config repository is a flat map and enumerator is a name an application could plausibly use for its own file.

  • Publish tags are vendor-scoped: enumerator-config → laranail::enumerator-config, and the same for -lang, -views, -stubs, -migrations, -js and -presets.

Fixed

  • SupportsNamespacedNames read $commandAliases without declaring it. Any command that used the trait without declaring the property died with Undefined property the moment Laravel built it — a trait that requires an undeclared property of its user is a trap, and the failure lands at boot rather than at the call site. It is declared on the trait now, defaulting to empty; a command wanting aliases still declares its own list and overrides it.

  • Published translations went to the wrong directory. The namespace registered is laranail-enumerator but the publish target was lang/vendor/enumerator, and Laravel looks for overrides under lang/vendor/{namespace} — so every published translation was silently ignored while the packaged default kept answering. Stubs moved the same way, to resources/stubs/laranail-enumerator.

  • Guarded by tests/Feature/NamingConventionTest.php, which reads the console kernel, the config repository, Lang::getLoader()->namespaces(), the view finder and ServiceProvider::publishableGroups() on a booted app.

Added

  • Presets\Enums\AlertTypeEnum — the twenty-seventh preset: the styling of a flash message or inline notice (default, primary, success, info, warning, error, mono).

    Distinct from SeverityEnum, which classifies a log record on the RFC 5424 scale — Primary and Mono are not severities and nothing here maps to a syslog level.

    Migrated from a class-constant enum in a consuming application, where a public Request::alert($message, string $type) macro passed arbitrary strings to new AlertType($type). Its icon() was a match with no default arm, so any value outside the seven constants threw UnhandledMatchError from a helper whose only job was showing the user a message. As a native enum that failure mode does not exist rather than being patched.

    color() and ->value deliberately differ for two cases: the backing values are the CSS tokens a front end already uses (default, mono), while color() answers with this package's palette (secondary, ghost).

  • Rector\RectorClassConstEnumToEnumerator — a third migration codemod, for the class-constant enum base an application invented for itself rather than a named vendor package. Configurable with the base classes to match, and it does nothing until told.

    Unlike the other two rules it rebases onto AbstractEnumeratorClass rather than flipping to a native enum, because a class-constant enum's cases are string constants and its methods are full of match ($this->value) { self::ACTIVE => … }. Promoting self::ACTIVE to an enum case would leave the class compiling, the suite running, and every one of those comparisons silently falling to its default.

    It rewrites extends, turns $this->value into $this->getValue() — not cosmetic, since $value is private on the new base, so a missed one is a fatal error rather than a type error — renames from/of → fromValue and tryFrom → tryFromValue, and drops $langPath rather than guessing a translation mapping it cannot see.

  • Translation support on the class-constant path. HasClassEnumBehavior now composes IsTranslatable, so an AbstractEnumeratorClass subclass implementing Contracts\Translatable resolves label(), description(), help() and placeholder() through the translator.

    This closes a gap that bit exactly the enums the class-const path exists for. AbstractEnumeratorClass is the documented migration target for legacy class-constant enums, and those are precisely the ones already carrying a translation namespace — but only native enums got IsTranslatable, through BehaviorCore. Migrating onto the class-const path therefore downgraded every translated label to a humanised case name: nothing errored, the strings just changed and the existing lang files went quietly unused.

    IsTranslatable's caseKey() and caseName() were written for native enums and read ->value and ->name as properties. On a class-const enum the backing property is private, so those were undefined-property reads; both now dispatch on the shape and use getValue()/getKey().

    An enum with no translations registered is unaffected — the fallback chain still ends at the #[Label] attribute and then the humaniser.

Fixed

  • HasOrder::compareTo() sorted an unorderable case first instead of last. It read a foreign case's position as (int) $other->getOrder(), and (int) null is 0 — so an enum whose getOrder() could not answer sorted before everything, the opposite of the documented default that a case with no order sorts last (PHP_INT_MAX). It now narrows with is_int() and falls back to the #[Order] attribute.

    method_exists() proves the method is callable and says nothing about what it returns, which is also why this showed up as 25 identical cast.int entries in the PHPStan baseline — one per enum using the trait. Fixing the trait cleared all of them.

Changed

  • rector/rector moved from suggest to require-dev. The migration rules were previously unrunnable in this package's own suite, which is why the two existing ones assert their shape rather than their output. The new rule is covered by a test that runs the codemod through the Rector binary over a fixture and reads the transformed file.

Initial public release.

0.4.0

The hardening + parity pass. Closes parity gaps left by v0.3.0 (dropdown wire:model + a11y + runtime i18n), pushes coverage past the long-standing 85 % threshold, halves the PHPStan baseline noise, and lands the v0.x → v1.0 lookahead.

Backwards compatible with v0.3.0 — no shipped public API removed or renamed; every new prop / method is opt-in additive.

Added

  • Runtime i18n for the multi-select Alpine dropdown (PR-ρ). v0.3.0 shipped nine hardcoded English strings inside the Blade view + Alpine x-data state (search placeholder, empty-state, clear / pill / announcement labels). PR-ρ routes every user-facing string through the Laravel translator under enumerator::enumerator.components.dropdown.*, with English defaults seeded in lang/en/enumerator.php. The Blade\Components\Dropdown::resolveDropdownStrings() helper splits :label-bearing patterns into a JS-prefix + the placeholder so Alpine can substitute the option label at runtime; non-pattern strings flow through directly. 4 new feature tests pin the en defaults AND the locale-override behaviour (fr / es overlays via Translator::addLines()). Closes the gap between ADR-0003's stated i18n-pluggable posture and the v0.3.0 dropdown surface. See ADR-0008 for the reaffirm-en-only-default decision and the flip conditions.
  • A11y wiring on the Alpine dropdown listbox (PR-ο). Brings the multi-select dropdown's keyboard + screen-reader behaviour up to the WAI-ARIA Authoring Practices Guide combobox-with-listbox pattern: aria-controls linking the trigger / search input to the listbox <ul id="{name}-listbox">, aria-activedescendant bound to the currently-active option's id ({name}-opt-{idx}), per-<li> ids, plus the existing aria-multiselectable / aria-selected / pill aria-label posture from v0.3.0. New opt-in :announce-changes="true" prop emits a polite aria-live="polite" aria-atomic="true" <span> near the wrapper that Alpine populates with Added <label> / Removed <label> / Selected <label> / Selection cleared. 9 new feature tests. Browser-level screen-reader validation deferred to v0.5.0.
  • IsCacheKey driver-matrix test coverage (PR-ξ). v0.3.0 PR-η shipped IsCacheKey tested only against the array cache driver. New feature suite at tests/Feature/Concerns/IsCacheKeyDriverMatrixTest.php pins put / get / cached / forget / remember / increment / decrement roundtrips against the file driver and the database driver (cache table created on the in-memory sqlite connection via the same shape as Testbench's stub migration). Redis is documented as a CI follow-up — needs a redis sidecar the local test harness can't reliably bind. The trait is now demonstrably driver-agnostic on the two drivers Laravel ships out of the box.
  • WithEnumTransitions bulk + validation helpers (PR-ν). Two new methods on the v0.3.0 Livewire trait. bulkTransitionEnum( $paths, $target) advances many Stateful properties to the same target case in one call (for "approve all" / "ship all" actions); per-path failures are independent. transitionEnumOrValidate( $path, $target, $messages) matches the original transitionEnum semantics but accepts caller-overridable error text for the invalid and notStateful failure modes — useful when the framework-default message doesn't fit UX copy. Both methods reuse the existing error-bag + dispatch wiring; no breakage to v0.3.0 callers. 7 new feature tests.
  • Dropdown component :wireModel / :wireModelModifier props (PR-μ). Mirror of the PR-γ pattern on radio + checkboxes — closes the parity gap left in v0.3.0. Emits wire:model[.modifier]="..." on the hidden <input>(s) for the Alpine listbox path (one per selected value in multi-select via <template x-for>) and on the <select> element for the native fallback path. Values flow through htmlspecialchars(..., ENT_QUOTES, 'UTF-8', false) per the D75 escape discipline. New Dropdown contract test extends the existing Select / Radio / Checkboxes no-extraAttrs arch tests. 9 new feature tests cover the four emission shapes (single/multi × Alpine/native) + escape regressions.

Changed

  • PHPStan baseline: halved (PR-θ). The PR-α residue analysis (v0.3.0) flagged ~120 patterns over the ≤ 600 target left after the v0.3.0 freeze. Most were trait-cascade noise: every new consumer enum that uses the HasEnumeratorBehavior umbrella retriggers the same defensive type-check warnings from the trait bodies. PR-α had path-scoped src/Concerns/* itself; PR-θ extends the same path-scope pattern to tests/Fixtures/Enums/* and src/Presets/Enums/* (the two big consumer dirs) plus the magic-method tests (HasEnumAttributesTest, MagicComparisonsTest, Modules/PestTest) and EnumeratorServiceProvider's container- return-typed argument leaks. Result: baseline errors 1480 → 704 (−52 %), file 4615 → 2179 lines (−53 %), unique entries ~360 (under the ≤ 600 PR-α target). reportUnmatchedIgnoredErrors: true flip stays deferred to v0.5.0.

Docs

  • v1.0.0 roadmap doc (PR-σ). New .design/plans/v1.0.0-roadmap.md enumerates three buckets: (a) public surface stable enough to lock at v1.0.0 (every Contract interface, the always-on trait set, all 9 opt-in feature traits, 4 Casts, 9 Attributes, Helpers + Facade, 9 Blade components, cache surface); (b) experimental surface that needs more v0.x bake time (WithEnumTransitions real-Livewire roundtrip, EnumeratorCasts, StructuredOutput emitters, Lighthouse / Saloon / Octane / Pest modules, Filament / Inertia / Nova integrations); (c) breaking changes worth saving for v1.0.0 (drop dead defensive guards, tighten mixed widening, split HasEnumerator umbrella, opt-in HasTransitions, config-key rename). Plus a v0.x → v1.0 cycle sequence.

Tests

  • Coverage push (PR-ι + PR-λ). AttributesCache 77.6 % → 99.2 % via 8 new branch-coverage tests (mixed-bit / duplicate-bit / non- power-of-two / class-const-bit fixtures). LaravelTranslatorAdapter 62.5 % → 87.5 % via stub-translator container binding to exercise the try / catch (\Throwable) branches in translate() and has(). Total src/ coverage: 83.40 % → 85.10 %. The CI --min=83 floor remains unchanged; per the v0.2.0 → v0.2.1 lesson, the gate moves to --min=85 once the measured number sustains across two consecutive releases — v0.4.0 is the first.
  • extraAttrs contract tests (PR-π) on Radio and Checkboxes, mirroring the existing Select arch test. Pins the trust contract that {!! \$extraAttrs !!} only receives ComponentAttributeBag- built (HTML-escaped) input.
0.3.0

The integration-depth + foundation pass. Adds Livewire state-transition tooling, cache-key enum encapsulation, multi-select Alpine dropdown, per-input Livewire bindings on radio / checkboxes, Codecov + mutation-testing CI channels, and a 39 %-pattern / 32 %-occurrence shrink of the PHPStan baseline.

Backwards compatible with v0.2.x consumers — no shipped public surface removed, no public method renamed. The only behaviour change worth flagging: <x-...::dropdown :multiple="true"> paired with :searchable="true" or :clearable="true" now renders the Alpine listbox instead of falling through to the native <select multiple>. The form-submission shape (one hidden input per selected value with name="…[]") matches what the native select would have submitted, so server-side consumers see the same payload.

Added

  • infection/infection mutation testing infrastructure (PR-ε sub-batch). New infection.json5 config targeting the core src/Concerns, src/Helpers, src/Casts, src/Rules, src/Support surface (modules / integrations / blade-views / etc. excluded). New .github/workflows/infection.yml runs mutation testing with pcov coverage on push to main + on PRs touching src/ or tests/. Non-blocking on first runs (continue-on-error: true) — pure measurement until a baseline kill rate is observed. No minMsi / minCoveredMsi gate yet; promotion to gating follows the same coverage-then-gate discipline as v0.2.0/v0.2.1.
  • IsCacheKey trait + Cacheable contract (PR-η). Define an enum case as a cache-key namespace, with shorthand methods proxying to Laravel's Cache facade — put() / get() / forget() / cached() / remember() / increment() / decrement(). The presence probe is cached() rather than has() so the trait composes with HasEnumeratorBehavior (which ships a static Enumerator::has(target) membership check) without a fatal trait-method collision; the regression is pinned by a composed-enum feature test. Standalone trait (not in the HasEnumerator umbrella) so a cache-key enum doesn't accidentally pull in label / colour / icon surface area when only the cache shape is wanted. Override key() in the consumer enum for a non-trivial cache-key shape (e.g., 'settings:' . $this->value). 13 feature tests against Laravel's array-cache driver.
  • WithEnumTransitions Livewire trait (PR-ζ). Promotes the existing docs/recipes/livewire-state-transitions.md recipe to a first-class trait under src/Integrations/Livewire/. Two methods: transitionEnum($propertyPath, $target) advances the state and dispatches an enumerator.transitioned Livewire event on success (or adds the InvalidTransitionException message to the Livewire error bag on failure); canTransitionEnum(...) pre-flights without mutating state. Uses data_get() / data_set() so nested property paths like 'order.status' resolve through Livewire's getter chain. 6 feature tests with a stubbed-error-bag fixture (no full Livewire request lifecycle required).
  • Codecov coverage reporting (PR-ε). phpunit.xml <report> block now emits coverage.xml (Clover format) alongside the existing text summary. New .github/workflows/coverage.yml runs Pest with coverage on PHP 8.3 on every push to main (and on PR), uploads to Codecov via codecov/codecov-action@v4. README carries a Codecov badge. The CI --min=83 floor in ci.yml is unchanged — coverage.yml is a measurement / publication channel, not a gate. coverage.xml is gitignored. One-time setup required: Codecov v4 dropped tokenless uploads even for public OSS repos. Set the CODECOV_TOKEN GitHub secret on the repo to enable badge updates — instructions in .github/workflows/coverage.yml header comment. Until then, the workflow succeeds (fail_ci_if_error: false) but the badge stays "unknown".
  • Multi-select Alpine listbox for <x-...::dropdown> (PR-δ). The Alpine path now handles :multiple="true" instead of falling through to the native <select multiple>. New shape: pill UI in the trigger (each pill with a × remove button), hidden inputs emitted via <template x-for> so the form receives the array, panel stays open on each selection toggle, aria-multiselectable="true" on the listbox <ul>, and a change event carrying the full array. Single- select mode is unchanged. disabled=true still falls through to the native <select multiple>. See docs/tools/blade-components.md "Multi-select mode" section. Tests: 10 new in tests/Feature/Blade/DropdownMultiSelectTest.php.
  • Per-input :wireModel prop on radio + checkboxes (PR-γ). <x-...::radio> and <x-...::checkboxes> accept a dedicated wireModel prop that emits wire:model="..." on every <input> element (in addition to the existing attribute-bag forwarding on the <fieldset>). The wireModelModifier companion prop emits wire:model.live="..." / wire:model.blur="..." / wire:model.debounce.500ms="..." etc. This is the required shape for Livewire 3 checkbox-array binding (array values won't populate from wire:model on the wrapping <fieldset>). v0.2.0 attribute-bag-forwarded wire:model on the fieldset still works for single radios via Livewire's DOM-morph, but the :wireModel prop is the recommended pattern. See docs/tools/blade-components.md "Livewire integration".

Fixed (pre-tag audit, 2026-05-16)

  • XSS hardening: #[Icon] attribute now HTML-escaped on render. The _base/badge.blade.php and _base/element.blade.php base partials used to emit {!! $caseIcon !!} (raw). Every shipped preset enum uses a plain class-name string ('check-circle', 'arrow-down', etc.), so escaping is a no-op for normal usage — but the #[Icon] value is also overridable at runtime via enumerator.overrides config and TenantContext::overridesFor(). A tenant-supplied override containing HTML/JS would have become a stored XSS. Switched to {{ $caseIcon }} so the value is always HTML-escaped. Pinned by tests/Feature/Blade/IconEscapingTest.php. Consumers who actually want raw SVG icons should publish the view bundle and customise per their framework variant.
  • Defensive escape: :wireModel / :wireModelModifier props on radio + checkboxes are now htmlspecialchars()-encoded before being concatenated into the wire:model="..." attribute string. Values are typically developer-controlled, but defensive escaping protects against attribute-breakout if either prop ever carries an unescaped " or &. The browser decodes attribute entities before Livewire reads the value, so the change is invisible at the JS layer. Pinned by tests/Feature/Blade/WireModelEscapingTest.php.
  • LaravelTranslatorAdapter now honours an explicit locale strictly. When translate($key, $replace, $locale) was called with an explicit non-null $locale and that locale had no entry, the adapter used to return the Laravel-fallback locale's value (typically en). That silently defeated IsTranslatable::label($locale)'s fallback chain (caller relies on null here to fall through to #[Label] then Humanizer::humanize()). The adapter now passes the third $fallback argument to Lang::has() / Lang::get(): false when the caller supplied an explicit locale (no fallback wanted), true when the caller passed null (resolve via the app's current locale
    • its fallback). Pinned by the three new tests in tests/Unit/Translations/LaravelTranslatorAdapterTest.php.
0.2.1

Hotfix. v0.2.0's CI failed on the merge commit because the coverage gate I raised from 80 → 85 in PR-A was set above what was actually measurable. Coverage on main is 83.2% — most of the new v0.2.0 tests exercise Blade view rendering (excluded from <source> in phpunit.xml), so src/ line coverage didn't move much. Lowering the gate to 83% so the floor matches reality. Target is still 90% on stable releases; the path to 90% goes through covering uncovered src/ branches, not through tightening the gate first.

Fixed

  • .github/workflows/ci.yml Pest step --min=85 → --min=83.
  • composer.json test:coverage script --min=85 → --min=83.
  • Comment in ci.yml documents the rationale + the 90% target.

No code path touched. Behaviour identical to v0.2.0.

0.2.0

The integration-rich Laravel enum toolkit pass. Adds Alpine.js loader infrastructure with CDN-first + local-fallback, an Alpine-enhanced searchable / clearable dropdown, Livewire-aware attribute forwarding on the select / radio / checkboxes components, Livewire promoted to require-dev so the integration is CI-tested, documentation reorganisation, and a release-messaging cleanup.

Backwards compatible with v0.1.0 consumers — no shipped surface removed, no public method renamed. The only behaviour change is a single defensive escape correction in RendersHtml::toHtml() (see Fixed below), which affects only the pre-encoded-entity edge case.

Changed

  • Softened the public tagline from the prior "Laravel enum godfather" framing to "the integration-rich Laravel enum toolkit". Reflects the v0.1.0 breadth honestly without overstating depth in any single integration axis.
  • Aligned the CI coverage gate, the composer test:coverage script, and CI narrative on --min=85 (was an inconsistent mix of 80 / 90 across surfaces). Currently 83.2% measured on CI — the new gate is set just above that line, and the next batches add coverage on the Livewire integration to clear it.
  • Reordered the orchestra/testbench constraint to ^10.0|^11.0 for readability. Resolved version range unchanged.

Added

  • <x-laranail-enumerator::alpine-loader /> Blade component for the Alpine-enhanced components landing in later v0.2.0 batches. CDN-first with local fallback; conflict-check guard prevents double-loading when consumers already ship Alpine via npm. Pinned Alpine version
    • SHA-384 SRI live in config/enumerator.php under alpine.*; CI verifies the bundled resources/js/alpine.min.js matches the integrity hash on every push. Opt out of the CDN with :cdn="false". See docs/tools/alpine-loader.md.
  • New publish tag enumerator-js copies the Alpine bundle into public/vendor/laranail-enumerator/ for the local-fallback path.
  • livewire/livewire ^3.5 added to require-dev so the Livewire integration (src/Integrations/Livewire/) is now exercised on the full CI matrix. EnumeratorCasts covered by 7 feature tests. Livewire stays in the suggest block — it's still an opt-in for consumers, just now CI-gated for the maintainer.
  • Livewire-aware Blade components. <x-...::select>, <x-...::radio>, and <x-...::checkboxes> now forward arbitrary caller attributes (wire:model.*, wire:loading, data-*, aria-*, x-*, etc.) through to their outer element via Laravel's ComponentAttributeBag::except(). The select binds on <select>; the radio / checkboxes bind on the wrapping <fieldset> (Livewire 3 morphs the value down to each <input>). Per-input wireModel prop deferred to v0.3.0. ADR-0004.
  • <x-...::checkboxes> (and <x-...::radio>) base views now emit the forwarded-attribute string on the wrapping <fieldset>. Was previously only the bootstrap variant of <x-...::select>.
  • docs/tools/blade-components.md updated with the component matrix, the Livewire integration contract, and the reserved-prop set per component.
  • Alpine-enhanced <x-laranail-enumerator::dropdown>. Set :searchable="true" or :clearable="true" and the component renders an accessible combobox/listbox driven by Alpine.js: type-to-filter, arrow / Enter / Esc keyboard navigation, click- outside close, ARIA roles, optional clear button, empty-state row, and a hidden <input> for native form submission. With both flags off, the native <select> from v0.1.0 still renders — fully backwards-compatible. Requires <x-...::alpine-loader /> once in the page. Multi-select + disabled fall back to the native select (multi-listbox is on the v0.3.0 backlog). ADR-0004 / ADR-0005. Documentation: docs/tools/blade-components.md "Dropdown — Alpine-enhanced searchable / clearable" section.

Fixed

  • README quality stat no longer quotes a numeric test / assertion count (the figure was stale within one commit of being written).
  • The v0.1.0 Filament integration line in this file referred to "column helpers". The shipped surface is more specific: EnumeratorColumn for tables and EnumeratorEntry for infolists, documented at docs/tools/blade-components.md and the integration recipes. (Historical [0.1.0] entry kept verbatim per Keep-a-Changelog discipline.)
  • Concerns\RendersHtml::toHtml() and Concerns\HasClassEnumBehavior::toHtml() now pass doubleEncode: false to Laravel's e() helper. Before this fix, an already-HTML-encoded entity in the label / classes / icon (e.g. a translator that returned &amp; instead of &) was re-encoded to &amp;amp; and rendered as visible &amp; text in the browser. Plain strings (which are the documented contract for translations) produce identical output — only the pre-encoded edge case changes behaviour. Caught by the new RendersHtmlEscapeTest regression suite. Issue S-3.
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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style