olivermbs / laravel-enumshare
Export PHP Enums to TypeScript/Inertia with labels, metadata, and type-safe frontend access
Fund package maintenance!
Oliver Smith
Requires
- php: ^8.3
- illuminate/console: ^10.0||^11.0||^12.0
- illuminate/contracts: ^10.0||^11.0||^12.0
- symfony/finder: ^6.0||^7.0
Requires (Dev)
- larastan/larastan: ^2.9||^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1||^7.10.0
- orchestra/testbench: ^10.0.0||^9.0.0||^8.22.0
- pestphp/pest: ^2.0||^3.0
- pestphp/pest-plugin-arch: ^2.5||^3.0
- pestphp/pest-plugin-laravel: ^2.0||^3.0
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^1.1||^2.0
- phpstan/phpstan-phpunit: ^1.3||^2.0
This package is auto-updated.
Last update: 2025-08-12 17:57:26 UTC
README
Export PHP Enums to TypeScript with labels, metadata, and type-safe frontend access. Generate TypeScript definitions for seamless enum sharing between Laravel backends and frontend applications.
New in v0.1.0-alpha: Zero runtime dependencies, comprehensive JSDoc documentation, 10+ utility methods, and smart type detection. Now generates pure TypeScript with no build requirements!
Installation
You can install the package via composer:
composer require olivermbs/laravel-enumshare
Publish the configuration file:
php artisan vendor:publish --tag="enumshare-config"
TypeScript Configuration
The generated TypeScript files are compatible with ES5+ and don't require any special configuration.
Quick Start
1. Create an Enum
Create an enum with the SharesWithFrontend
trait:
<?php namespace App\Enums; use Olivermbs\LaravelEnumshare\Attributes\ExportMethod; use Olivermbs\LaravelEnumshare\Attributes\Label; use Olivermbs\LaravelEnumshare\Attributes\Meta; use Olivermbs\LaravelEnumshare\Attributes\TranslatedLabel; use Olivermbs\LaravelEnumshare\Concerns\SharesWithFrontend; enum TripStatus: string { use SharesWithFrontend; #[TranslatedLabel('trips.saved')] #[Meta(['color' => 'gray', 'icon' => 'save'])] case Saved = 'saved'; #[Label('Confirmed Trip')] #[Meta(['color' => 'green', 'icon' => 'check'])] case Confirmed = 'confirmed'; case Cancelled = 'cancelled'; #[ExportMethod] public function isActive(): bool { return $this !== self::Cancelled; } }
2. Configure Enums
Add enums to the config file:
// config/enumshare.php return [ 'enums' => [ App\Enums\TripStatus::class, App\Enums\UserRole::class, ], 'export' => [ 'path' => resource_path('js/enums'), ], ];
3. Auto-Regeneration (Optional)
Vite Wayfinder (Recommended)
npm install --save-dev @laravel/vite-plugin-wayfinder
// vite.config.js import { wayfinder } from '@laravel/vite-plugin-wayfinder'; export default defineConfig({ plugins: [ // ... other plugins wayfinder({ command: 'php artisan enums:export --force', patterns: ['app/Enums/**/*.php', 'lang/*/enums.php', 'config/enumshare.php'], }), ], });
Manual Watch
php artisan enums:watch
4. Export and Use
php artisan enums:export
Import directly in your frontend:
import { TripStatus } from '@/enums/TripStatus'; // Type-safe access with IntelliSense console.log(TripStatus.Saved.value); // 'saved' console.log(TripStatus.Saved.label); // 'Trip Saved' console.log(TripStatus.Saved.meta); // { color: 'gray', icon: 'save' } // Utility methods console.log(TripStatus.keys()); // ['Saved', 'Confirmed', 'Cancelled'] console.log(TripStatus.values()); // ['saved', 'confirmed', 'cancelled'] console.log(TripStatus.options); // [{ value: 'saved', label: 'Trip Saved' }, ...] // Lookup methods console.log(TripStatus.from('saved')); // TripStatus.Saved entry console.log(TripStatus.fromKey('Saved')); // TripStatus.Saved entry // Validation methods console.log(TripStatus.isValid('saved')); // true console.log(TripStatus.hasKey('Saved')); // true console.log(TripStatus.count); // 3 // Functional utilities console.log(TripStatus.random()); // Random enum entry console.log(TripStatus.filter(e => e.meta.color === 'green')); // [Confirmed entry] console.log(TripStatus.map(e => e.label)); // ['Trip Saved', 'Confirmed Trip', 'Cancelled'] // Use in conditionals if (trip.status === TripStatus.Confirmed.value) { // Handle confirmed trip }
Features
- Zero runtime dependencies - Pure static TypeScript generation, no build dependencies
- Rich utility methods - 10+ utility methods including validation, filtering, and random selection
- Comprehensive JSDoc - Full documentation with examples for perfect IDE IntelliSense
- Smart type detection - Automatic PHP → TypeScript type mapping for meta properties
- Mixed label support - Handles both simple strings and multilingual translations
- Auto-regeneration - Automatic updates during development with Vite Wayfinder
- Attributes -
@Label
,@TranslatedLabel
,@Meta
,@ExportMethod
for rich enum data - Multi-locale - Built-in translation support for international apps
- TypeScript integration - Strict definitions with IntelliSense support
- Method export - Export computed properties and business logic
Rich TypeScript API
Every generated enum comes with comprehensive utility methods and full JSDoc documentation:
Validation & Info
TripStatus.isValid('saved') // boolean - Check if value exists TripStatus.hasKey('Saved') // boolean - Check if key exists TripStatus.count // number - Total enum entries
Functional Utilities
TripStatus.random() // Get random enum entry TripStatus.filter(e => e.meta.color === 'green') // Filter entries TripStatus.map(e => e.label) // Transform entries TripStatus.find(e => e.value === 'saved') // Find first match TripStatus.some(e => e.meta.urgent) // Test if any match TripStatus.every(e => e.value !== null) // Test if all match
IDE Support
- Full JSDoc documentation with usage examples
- Perfect IntelliSense - Hover over any method for help
- Type-safe parameters - TypeScript catches invalid usage
- Auto-completion - IDE suggests all available methods
Commands
php artisan enums:export # Export enums php artisan enums:export --locale=es # Export specific locale php artisan enums:export-all-locales # Export all locales php artisan enums:watch # Watch for changes php artisan enums:discover # Discover enums
Translations
TranslatedLabel Attribute
Use @TranslatedLabel
for translation-based labels instead of hardcoded strings:
enum OrderStatus: string { use SharesWithFrontend; #[TranslatedLabel('orders.pending')] case Pending = 'pending'; #[TranslatedLabel('orders.confirmed', ['status' => 'active'])] case Confirmed = 'confirmed'; #[Label('Cancelled')] // Mix with regular labels case Cancelled = 'cancelled'; }
Multi-Locale Support
Configure multiple locales to export all translations:
// config/enumshare.php 'export' => [ 'locales' => ['en', 'fr', 'es'], ],
Create translation files:
// lang/en/orders.php return [ 'pending' => 'Pending Order', 'confirmed' => 'Confirmed :status Order', ]; // lang/fr/orders.php return [ 'pending' => 'Commande en attente', 'confirmed' => 'Commande :status confirmée', ];
Output Format
Single locale (when no locales
configured):
{ label: "Pending Order" }
Multiple locales:
{ label: { en: "Pending Order", fr: "Commande en attente", es: "Pedido pendiente" } }
Method Export
Export method results as static properties with @ExportMethod
:
Basic Usage
<?php namespace App\Enums; use Olivermbs\LaravelEnumshare\Attributes\ExportMethod; use Olivermbs\LaravelEnumshare\Concerns\SharesWithFrontend; enum ContactType: int { use SharesWithFrontend; case EMAIL = 1; case PHONE = 2; case SMS = 3; case POSTAL = 4; public function label(): string { return match ($this) { self::EMAIL => 'Email', self::PHONE => 'Phone', self::SMS => 'SMS', self::POSTAL => 'Postal Mail', }; } #[ExportMethod] public function isInstant(): bool { return $this !== self::POSTAL; } #[ExportMethod('requiresPhoneNumber')] public function needsPhone(): bool { return match ($this) { self::PHONE, self::SMS => true, default => false, }; } }
Generated TypeScript
The exported TypeScript will include method results as properties:
// ContactType.ts (generated) /** * ContactType enum generated from App\Enums\ContactType * * @example * // Access enum entries * ContactType.EMAIL.label // "Email" * ContactType.PHONE.isInstant // true * * // Lookup by value * ContactType.from(1) // EMAIL entry * ContactType.from(2) // PHONE entry */ export const ContactType = { /** Email */ EMAIL: { key: 'EMAIL', value: 1, label: 'Email', meta: {}, isInstant: true, requiresPhoneNumber: false }, /** Phone */ PHONE: { key: 'PHONE', value: 2, label: 'Phone', meta: {}, isInstant: true, requiresPhoneNumber: true }, // Rich utility methods with full JSDoc isValid: (value: unknown): boolean => { /* ... */ }, random: (): ContactTypeEntry => { /* ... */ }, filter: (predicate: (entry: ContactTypeEntry) => boolean) => { /* ... */ }, // ... 10+ more methods } as const;
Frontend Usage
Access method results as properties in your frontend code:
import { ContactType } from '@/enums/ContactType'; // Access computed properties console.log(ContactType.EMAIL.isInstant); // true console.log(ContactType.POSTAL.isInstant); // false console.log(ContactType.PHONE.requiresPhoneNumber); // true // Use in conditional logic const contactMethods = ContactType.entries.filter(method => method.isInstant && !method.requiresPhoneNumber ); // Returns: [EMAIL entry] // React/Vue component example const ContactForm = () => { const [selectedType, setSelectedType] = useState(ContactType.EMAIL.value); const selectedEntry = ContactType.from(selectedType); return ( <div> <select onChange={(e) => setSelectedType(e.target.value)}> {ContactType.options.map(option => ( <option key={option.value} value={option.value}> {option.label} </option> ))} </select> {selectedEntry?.requiresPhoneNumber && ( <input type="tel" placeholder="Phone number required" /> )} {selectedEntry?.isInstant && ( <p>This contact method delivers instantly!</p> )} </div> ); };
Custom Property Names
Use the optional parameter to customize the exported property name:
#[ExportMethod('isUrgent')] public function canHandleUrgentRequests(): bool { return $this === self::PHONE || $this === self::EMAIL; }
Requirements
- Methods must be public and take no parameters
- Methods should return serializable values (bool, string, int, array, etc.)
- Methods that throw exceptions will be skipped during export
- Only methods with the
@ExportMethod
attribute are exported
Use Cases
- Business logic: Export validation rules, permissions, or business constraints
- UI helpers: Color schemes, icons, display preferences
- Computed properties: Derived values based on enum state
- Feature flags: Enable/disable functionality per enum case
enum UserRole: string { use SharesWithFrontend; case ADMIN = 'admin'; case MODERATOR = 'moderator'; case USER = 'user'; #[ExportMethod] public function canModerate(): bool { return $this !== self::USER; } #[ExportMethod('hasFullAccess')] public function isAdmin(): bool { return $this === self::ADMIN; } #[ExportMethod] public function getPermissionLevel(): int { return match($this) { self::ADMIN => 100, self::MODERATOR => 50, self::USER => 10, }; } }
Auto-Discovery
Automatically find and register enums:
Enable Auto-Discovery
// config/enumshare.php 'autodiscovery' => [ 'enabled' => true, 'paths' => [ 'app/Enums', 'app/Domain/*/Enums', ], ],
Discovery Commands
# Discover enums and show what was found (always fresh - no caching)
php artisan enums:discover
How It Works
- Path Scanning: Scans configured directories for PHP files containing enums
- Trait Validation: Only includes enums using
SharesWithFrontend
trait - Combination: Merges discovered enums with manually configured ones
Environment Variables
# Enable/disable autodiscovery
ENUMSHARE_AUTODISCOVERY=true
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.