Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds two new artifacts to the laravel-enumify package: a SKILL.md documentation file for AI-powered "boost" workflows, and a new EnumHelpers PHP trait providing utility methods for backed enums. Together, these help developers use enums consistently across the PHP backend and TypeScript frontend.
Changes:
- Introduced
src/Concerns/EnumHelpers.php— a new trait with static utility methods (options,selectOptions,values,names,hasValue) for backed enums. - Added
resources/boost/skills/enumify/SKILL.md— comprehensive AI-skill documentation describing strict rules, workflow steps, configuration, and anti-patterns for Enumify usage. - Updated
.gitignoreto exclude.DS_Storefiles.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/Concerns/EnumHelpers.php |
New trait providing reusable static helper methods for backed enums |
resources/boost/skills/enumify/SKILL.md |
New AI boost skill documentation covering all aspects of Enumify usage |
.gitignore |
Added .DS_Store to ignored files |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| public static function options(string $label = 'label'): array | ||
| { | ||
| return collect(self::cases()) | ||
| ->mapWithKeys(fn (self $case): array => [$case->value => $case->{$label}()]) | ||
| ->all(); | ||
| } | ||
|
|
||
| /** | ||
| * Get all enum cases as an array of {value, label} objects for frontend selects. | ||
| * | ||
| * @return array<int, array{value: string|int, label: string}> | ||
| */ | ||
| public static function selectOptions(string $label = 'label'): array | ||
| { | ||
| return collect(self::cases()) | ||
| ->map(fn (self $case): array => [ | ||
| 'value' => $case->value, | ||
| 'label' => $case->{$label}(), | ||
| ]) | ||
| ->values() | ||
| ->all(); | ||
| } |
There was a problem hiding this comment.
The options() and selectOptions() methods call a dynamically-named method on each enum case ($case->{$label}()) with no check that the method actually exists on the enum. If an enum uses this trait but does not implement the method named by $label (defaulting to 'label'), or if a caller passes a method name that doesn't exist, PHP will throw a fatal Error ("Call to undefined method"). Consider adding a method_exists($case, $label) guard and throwing a descriptive \BadMethodCallException (or \InvalidArgumentException) with the enum class and missing method name, so callers get an actionable error message instead of a generic PHP fatal.
| 16. **When localization is enabled** (`react` or `vue` mode), ALWAYS use the hook function instead of the static Utils: | ||
| ```typescript | ||
| // With localization.mode = 'react' | ||
| const { label, color, options } = useOrderStatusUtils(); | ||
|
|
||
| // With localization.mode = 'vue' | ||
| const { label, color, options } = useOrderStatusUtils(); | ||
| ``` |
There was a problem hiding this comment.
Rule 16 in the documentation shows identical code snippets for both react and vue localization modes. The React and Vue hook usage patterns are identical, which is either intentional (and should be noted in the doc) or a copy-paste error where the Vue example should demonstrate a Vue-specific usage pattern (e.g. using ref / computed if the hook returns reactive values, or showing the Composition API style). This makes the section misleading to developers who need to understand the difference between the two modes.
| trait EnumHelpers | ||
| { | ||
| /** | ||
| * Get all enum cases as an array of value => label pairs. | ||
| * | ||
| * Calls the given method name on each case (defaults to "label"). | ||
| * Requires the enum to implement a public instance method with that name. | ||
| * | ||
| * @return array<string|int, string> | ||
| */ | ||
| public static function options(string $label = 'label'): array | ||
| { | ||
| return collect(self::cases()) | ||
| ->mapWithKeys(fn (self $case): array => [$case->value => $case->{$label}()]) | ||
| ->all(); | ||
| } | ||
|
|
||
| /** | ||
| * Get all enum cases as an array of {value, label} objects for frontend selects. | ||
| * | ||
| * @return array<int, array{value: string|int, label: string}> | ||
| */ | ||
| public static function selectOptions(string $label = 'label'): array | ||
| { | ||
| return collect(self::cases()) | ||
| ->map(fn (self $case): array => [ | ||
| 'value' => $case->value, | ||
| 'label' => $case->{$label}(), | ||
| ]) | ||
| ->values() | ||
| ->all(); | ||
| } | ||
|
|
||
| /** | ||
| * Get all enum values as an array. | ||
| * | ||
| * @return array<int, string|int> | ||
| */ | ||
| public static function values(): array | ||
| { | ||
| return array_column(self::cases(), 'value'); | ||
| } | ||
|
|
||
| /** | ||
| * Get all enum names as an array. | ||
| * | ||
| * @return array<int, string> | ||
| */ | ||
| public static function names(): array | ||
| { | ||
| return array_column(self::cases(), 'name'); | ||
| } | ||
|
|
||
| /** | ||
| * Check if a value exists in this enum. | ||
| */ | ||
| public static function hasValue(string|int $value): bool | ||
| { | ||
| return in_array($value, self::values(), true); | ||
| } | ||
| } |
There was a problem hiding this comment.
There are no tests for the newly introduced EnumHelpers trait. The project has comprehensive unit test coverage (with a 100% coverage requirement per composer.json's test-coverage script: --min=100). Tests should be added to cover all five methods: options(), selectOptions(), values(), names(), and hasValue(), including edge cases such as calling options() with a non-existent method name.
| * Get all enum cases as an array of {value, label} objects for frontend selects. | ||
| * | ||
| * @return array<int, array{value: string|int, label: string}> | ||
| */ | ||
| public static function selectOptions(string $label = 'label'): array |
There was a problem hiding this comment.
The @return docblock for selectOptions() declares label: string in the returned array shape, but the $label parameter accepts any method name, and that method may return a non-string type (e.g., int, bool). The docblock should use a more accurate return type like array{value: string|int, label: mixed}, or document that the $label method must return a string for this contract to hold.
| public function isEscalated(): bool | ||
| { | ||
| return in_array($this, [self::HIGH, self::URGENT]); |
There was a problem hiding this comment.
The isEscalated() example in the documentation calls in_array($this, [self::HIGH, self::URGENT]) without the strict (true) third argument. This is inconsistent with the codebase's own hasValue() in the trait (line 73), which correctly uses true for strict comparison, and could mislead developers into using loose comparisons when implementing similar methods.
This pull request introduces comprehensive documentation and utility code for the Enumify skill, which streamlines syncing PHP enums with TypeScript, enforces strict enum usage patterns, and provides helper methods for both backend and frontend development. The most important changes are grouped below:
Enumify skill documentation and workflow:
SKILL.mdfile for the Enumify skill, outlining strict rules for PHP enum creation, model casting, frontend usage, and anti-patterns to avoid. The documentation also describes the complete workflow for syncing enums and using them across backend and frontend, including artisan commands and configuration options.Backend utility trait for enums:
EnumHelperstrait insrc/Concerns/EnumHelpers.php, providing static methods for listing enum options, values, names, and checking value existence. These helpers simplify common enum operations and ensure consistency between backend and frontend representations.