├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── dev └── examples │ ├── backed-enum.php │ ├── has-attributes.php │ └── str-helpers.php ├── phpstan.neon.dist ├── phpunit.xml ├── pint.json ├── src ├── Attributes │ ├── Description.php │ ├── Id.php │ ├── Label.php │ └── Metadata.php ├── EnumServiceProvider.php ├── Enums │ ├── BackedEnumShape.php │ └── BackedEnumStatus.php ├── Facades │ └── Enum.php ├── Macros │ └── Str.php ├── Services │ ├── BackedEnumService.php │ ├── EnumService.php │ ├── HasAttributesService.php │ └── MetadataAccessor.php └── Traits │ ├── BackedEnum.php │ ├── ExtendsBackedEnum.php │ └── HasAttributes.php └── tests ├── Arch.php ├── Facades └── Enum.php ├── Pest.php ├── ServiceProvider.php └── Unit ├── Attributes.php ├── Attributes ├── AttributeCapabilitiesTest.php └── MetadataPropertyAccessTest.php └── Methods.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | .phpunit.result.cache 3 | .phpunit.cache 4 | composer.lock 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.1.0] - 2025-05-06 2 | 3 | ### Added 4 | 5 | - `meta()` accessor for property-style access to enum metadata. 6 | - `keyBy` support in `descriptions()`, `labels()`, `ids()`, and `metadatum()` for keyed associative outputs (by case `name` or `value`). 7 | - `mapCasesTo()` abstraction to DRY up attribute accessor logic. 8 | - New example enum class: `BackedEnumShape` (used in tests and documentation). 9 | - Pest tests to verify `meta()` accessor behavior. 10 | - README updates with new usage examples for `meta()` and `keyBy` features. 11 | 12 | ### Updated 13 | 14 | - Refactored `HasAttributesService` to use `mapCasesTo()` and support `keyBy`. 15 | - Updated `EnumService` and facade docblocks to reflect new method signatures. 16 | - Replaced old example enums in dev/test files with `BackedEnumShape`. 17 | 18 | ### Fixed 19 | 20 | - Minor documentation typos and improved consistency across example output. 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Jeramy Hing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Laravel ENUM

2 | 3 |

4 | Total Downloads 5 | Latest Stable Version 6 | License 7 |

8 | 9 | The **Laravel Enum** package enriches Laravel's enum support, integrating advanced features like attribute handling, select array transformation, and facade access for streamlined enum operations. Designed for Laravel applications, it offers a suite of utilities for both backed enums and attribute-enhanced enums, including descriptive annotations, ID management, label generation, and metadata association. This package streamlines working with enums in Laravel by providing intuitive, fluent interfaces for common tasks, enhancing enum usability in forms, API responses, and more. Whether you're defining select options, querying enum attributes, or integrating enums tightly with Laravel features, **Laravel Enum** simplifies these processes, making enum management in Laravel applications both powerful and efficient. Offered by iteks, Developed by jeramyhing. 10 | 11 | ## Get Started 12 | 13 | > **Requires PHP 8.1+** 14 | 15 | Install **Laravel Enum** via the Composer package manager: 16 | 17 | ```bash 18 | composer require iteks/laravel-enum 19 | ``` 20 | 21 | ## Usage 22 | 23 | - [Attributes](#attributes) 24 | - [Enum Helpers (BackedEnum)](#enum-helpers-backedenum) 25 | - [Enum::asSelectArray()](#enumasselectarray) 26 | - [Enum::toLabel()](#enumtolabel) 27 | - [Enum::toLabels()](#enumtolabels) 28 | - [Enum Helpers (HasAttributes)](#enum-helpers-hasattributes) 29 | - [Enum::attributes()](#enumattributes) 30 | - [Enum::description()](#enumdescription) 31 | - [Enum::descriptions()](#enumdescriptions) 32 | - [Enum::id()](#enumid) 33 | - [Enum::ids()](#enumids) 34 | - [Enum::label()](#enumlabel) 35 | - [Enum::labels()](#enumlabels) 36 | - [Enum::metadata()](#enummetadata) **filterable** 37 | - [Enum::metadatum()](#enummetadatum) **filterable** 38 | - [Enum Traits (BackedEnum)](#enum-traits-backedenum) 39 | - [asSelectArray()](#asselectarray) 40 | - [fromName()](#fromname) 41 | - [name()](#name) 42 | - [names()](#names) 43 | - [toLabel()](#tolabel) 44 | - [toLabels()](#tolabels) 45 | - [tryFromName()](#tryfromname) 46 | - [value()](#value) 47 | - [values()](#values) 48 | - [Enum Traits (HasAttributes)](#enum-traits-hasattributes) 49 | - [attributes()](#attributes) 50 | - [description()](#description) 51 | - [descriptions()](#descriptions) 52 | - [id()](#id) 53 | - [ids()](#ids) 54 | - [label()](#label) 55 | - [labels()](#labels) 56 | - [metadata()](#metadata) **filterable** 57 | - [metadatum()](#metadatum) **filterable** 58 | - [meta()](#meta) **Metadata property accessor** 59 | - [String Helper Macros](#string-helper-macros) 60 | - [Str::splitConstantCase()](#strsplitconstantcase) 61 | - [Str::splitEnumCase()](#strsplitenumcase) 62 | 63 | ## Attributes 64 | 65 | The **Laravel Enum** methods are designed for PHP 8 Backed Enumeration classes. 66 | 67 | **Laravel Enum** helper and trait methods extend an existing backed enum class for more versatile enum handling. Additionally, **Laravel Enum** offers a fluent way to add and manage PHP 8 Attributes on backed enum cases. This package comes with four available attributes to readily assign to your enum cases: **Description**, **Id**, **Label**, and **Metadata**. The example enum classes linked below demonstrate how you can apply these attributes to your enums. You may pick and choose which attributes you wish to take advantage of. 68 | 69 | - [BackedEnumShape.php](src/Enums/BackedEnumShape.php) 70 | - [BackedEnumStatus.php](src/Enums/BackedEnumStatus.php) 71 | 72 | To apply an attribute to an enum, be sure to import the `BackedEnum` and the `HasAttributes` traits and the attribute class needed. 73 | 74 | ```php 75 | use Iteks\Attributes\Description; 76 | use Iteks\Traits\BackedEnum; 77 | use Iteks\Traits\HasAttributes; 78 | 79 | enum BackedEnumShape: string 80 | { 81 | use BackedEnum; 82 | use HasAttributes; 83 | 84 | #[Description('A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center')] 85 | case RoundCircle = 'circle'; 86 | } 87 | ``` 88 | 89 | The package provides the following four attributes to enhance your enum classes and cases: 90 | 91 | ### Description Attribute 92 | 93 | Provides descriptive text for enum classes and cases. 94 | 95 | ```php 96 | enum BackedEnumShape: string 97 | { 98 | #[Description('A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center')] 99 | case RoundCircle = 'circle'; 100 | } 101 | ``` 102 | 103 | ### Id Attribute 104 | 105 | Provides unique identifiers for enum cases. 106 | 107 | ```php 108 | enum BackedEnumShape: string 109 | { 110 | #[Id(1)] 111 | case RoundCircle = 'circle'; 112 | } 113 | ``` 114 | 115 | ### Label Attribute 116 | 117 | Provides human-readable labels for enum cases. 118 | 119 | ```php 120 | enum BackedEnumShape: string 121 | { 122 | #[Label('Round Circle')] 123 | case RoundCircle = 'circle'; 124 | } 125 | ``` 126 | 127 | ### Metadata Attribute 128 | 129 | Provides additional metadata for enum classes and cases. Metadata can be specified as either an array or a JSON string (must use double quotes for valid JSON). 130 | 131 | ```php 132 | enum BackedEnumShape: string 133 | { 134 | #[Metadata(['color' => 'red', 'sides' => 0, 'type' => 'curved'])] // Metadata as array 135 | case RoundCircle = 'circle'; 136 | 137 | #[Metadata('{"color": "blue", "sides": 4, "type": "polygon", "regular": true}')] // Metadata as JSON string 138 | case BoxSquare = 'square'; 139 | } 140 | ``` 141 | 142 | The metadata values can be accessed in three ways: 143 | 144 | 1. Through the `meta()` accessor (recommended for property-like access) 145 | - [meta()](#meta) 146 | 2. Using the `metadata()` method with optional key filtering (recommended for case specific data) 147 | - [Enum::metadata()](#enummetadata) 148 | - [metadata()](#metadata) 149 | 3. Using the `metadatum()` method with optional key filtering (recommended for all enum cases within the class) 150 | - [Enum::metadatum()](#enummetadatum) 151 | - [metadatum()](#metadatum) 152 | 153 | > **Note**: While Id and Label attributes technically support class-level usage, this is deprecated and will be removed in v2.0.0. Please use these attributes only on enum cases. 154 | 155 | [top](#usage) 156 | 157 | ## Enum Helpers (BackedEnum) 158 | 159 | The **Laravel Enum** package provides a set of helper methods through the `Enum` facade to work with backed enums. These methods are available for any enum that uses the `BackedEnum` trait. 160 | 161 | To use the Enum facade, start by importing the Enum helper into the file where your logic will be: 162 | 163 | ```php 164 | use Iteks\Facades\Enum; 165 | ``` 166 | 167 | ### Enum::asSelectArray() 168 | 169 | Convert an enum to a select array format, useful for form select elements. 170 | 171 | The array will consist of a `text` key column containing values of the case name in display format, and a `value` keys column containing values using the original simpler values. 172 | 173 | _Note: This method will first check for **Label** and **Id** attributes applied to the target enum class. If they are present, the method will prioritize those values. If not present, the method will return a mutated Headline value from the case name._ 174 | 175 | ```php 176 | $options = Enum::asSelectArray(BackedEnumShape::class); 177 | ``` 178 | 179 | ```php 180 | // Result: 181 | [ 182 | ['text' => 'Round Circle', 'value' => 1], 183 | ['text' => 'Perfect Square', 'value' => 2], 184 | ['text' => 'Right-Triangle', 'value' => 3], 185 | ['text' => '5 Point Star', 'value' => 4], 186 | ['text' => 'Poly-Rectangle', 'value' => 5] 187 | ] 188 | ``` 189 | 190 | ### Enum::toLabel() 191 | 192 | Convert an enum case to its label representation. 193 | 194 | ```php 195 | $label = Enum::toLabel(BackedEnumShape::RoundCircle); 196 | ``` 197 | 198 | ```php 199 | // Result: 200 | 'Round Circle' 201 | ``` 202 | 203 | ### Enum::toLabels() 204 | 205 | Convert multiple enum cases to their label representations. 206 | 207 | ```php 208 | $labels = Enum::toLabels(); 209 | ``` 210 | 211 | ```php 212 | // Result: 213 | ['Round Circle', 'Box Square', 'Right Triangle', 'Pointed Star', 'Polygon Rectangle'] 214 | ``` 215 | 216 | [top](#usage) 217 | 218 | ## Enum Helpers (HasAttributes) 219 | 220 | The **Laravel Enum** package provides a rich set of helper methods through the `Enum` facade to work with enum attributes. These methods are available for any enum that uses both the `BackedEnum` and `HasAttributes` traits. 221 | 222 | To use the Enum facade, start by importing the Enum helper into the file where your logic will be: 223 | 224 | ```php 225 | use Iteks\Facades\Enum; 226 | ``` 227 | 228 | ### Enum::attributes() 229 | 230 | Get all attributes for an enum instance or all class instances. 231 | 232 | ```php 233 | $attributes = Enum::attributes(BackedEnumShape::RoundCircle); 234 | ``` 235 | 236 | ```php 237 | // Result: 238 | [ 239 | 'simpleValue' => 'circle', 240 | 'description' => 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 241 | 'id' => 1, 242 | 'label' => 'Round Circle', 243 | 'metadata' => [ 244 | 'color' => 'red', 245 | 'sides' => 0, 246 | 'type' => 'curved' 247 | ] 248 | ] 249 | ``` 250 | 251 | Filter attributes for the enum instance. 252 | 253 | ```php 254 | $attributes = Enum::attributes(BackedEnumShape::RoundCircle, ['id', 'label']); 255 | ``` 256 | 257 | ```php 258 | // Result: 259 | [ 260 | 'id' => 1, 261 | 'label' => 'Round Circle' 262 | ] 263 | ``` 264 | 265 | Get all attributes for each instance in the class. 266 | 267 | ```php 268 | $attributes = Enum::attributes(BackedEnumShape::class); 269 | ``` 270 | 271 | ```php 272 | // Result: 273 | [ 274 | 'RoundCircle' => [ 275 | 'simpleValue' => 'circle', 276 | 'description' => 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 277 | 'id' => 1, 278 | 'label' => 'Round Circle', 279 | 'metadata' => [, 280 | 'color' => 'red', 281 | 'sides' => 0, 282 | 'type' => 'curved' 283 | ] 284 | ], 285 | 'BoxSquare' => [ 286 | 'simpleValue' => 'square', 287 | 'description' => 'A square is a regular quadrilateral', 288 | 'id' => 2, 289 | 'label' => 'Perfect Square', 290 | 'metadata' => [ 291 | 'color' => 'blue', 292 | 'sides' => 4, 293 | 'type' => 'polygon', 294 | 'regular' => true 295 | ] 296 | ], 297 | 'RightTriangle' => [ 298 | 'simpleValue' => 'triangle', 299 | 'description' => 'A triangle is a polygon with three edges and three vertices', 300 | 'id' => 3, 301 | 'label' => 'Right-Triangle', 302 | 'metadata' => [, 303 | 'color' => 'green', 304 | 'sides' => 3, 305 | 'type' => 'polygon' 306 | ] 307 | ], 308 | ... 309 | ] 310 | ``` 311 | 312 | Filter all attributes for each instance in the class by key(s). 313 | 314 | ```php 315 | $attributes = Enum::attributes(BackedEnumShape::class, ['description', 'label']); 316 | ``` 317 | 318 | ```php 319 | // Result: 320 | [ 321 | 'RoundCircle' => [ 322 | 'description' => 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 323 | 'label' => 'Round Circle' 324 | ], 325 | 'BoxSquare' => [ 326 | 'description' => 'A square is a regular quadrilateral', 327 | 'label' => 'Perfect Square' 328 | ], 329 | 'RightTriangle' => [ 330 | 'description' => 'A triangle is a polygon with three edges and three vertices', 331 | 'label' => 'Right-Triangle' 332 | ], 333 | ... 334 | ] 335 | ``` 336 | 337 | ### Enum::description() 338 | 339 | Get the description for an enum case. 340 | 341 | ```php 342 | $description = Enum::description(BackedEnumShape::RoundCircle); 343 | ``` 344 | 345 | ```php 346 | // Result: 347 | 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center' 348 | ``` 349 | 350 | ### Enum::descriptions() 351 | 352 | Get descriptions for multiple enum cases. 353 | 354 | ```php 355 | $descriptions = Enum::descriptions(BackedEnumShape::class); 356 | ``` 357 | 358 | ```php 359 | // Result: 360 | [ 361 | 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 362 | 'A square is a regular quadrilateral', 363 | 'A triangle is a polygon with three edges and three vertices', 364 | 'A star is a self-intersecting, equilateral polygon', 365 | 'A rectangle is a quadrilateral with four right angles' 366 | ] 367 | ``` 368 | 369 | Use the `keyBy` argument to get an associative array using the case name or simple value as the keys/indices. 370 | 371 | ```php 372 | $descriptions = Enum::descriptions(BackedEnumShape::class, 'name'); 373 | ``` 374 | 375 | ```php 376 | // Result: 377 | [ 378 | 'RoundCircle' => 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 379 | 'BoxSquare' => 'A square is a regular quadrilateral', 380 | ... 381 | ] 382 | ``` 383 | 384 | ### Enum::id() 385 | 386 | Get the ID attribute for an enum case. 387 | 388 | ```php 389 | $id = Enum::id(BackedEnumShape::PointedStar); 390 | ``` 391 | 392 | ```php 393 | // Result: 394 | 4 395 | ``` 396 | 397 | ### Enum::ids() 398 | 399 | Get IDs for multiple enum cases. 400 | 401 | ```php 402 | $ids = Enum::ids(BackedEnumShape::class); 403 | ``` 404 | 405 | ```php 406 | // Result: 407 | [1, 2, 3, 4, 5] 408 | ``` 409 | 410 | Use the `keyBy` argument to get an associative array using the case name or simple value as the keys/indices. 411 | 412 | ```php 413 | $ids = Enum::ids(BackedEnumShape::class, 'value'); 414 | ``` 415 | 416 | ```php 417 | // Result: 418 | ['circle' => 1, 'square' => 2, 'triangle' => 3, 'star' => 4, 'rectangle' => 5] 419 | ``` 420 | 421 | ### Enum::label() 422 | 423 | Get the label for an enum case. 424 | 425 | ```php 426 | $label = Enum::label(BackedEnumShape::PolygonRectangle); 427 | ``` 428 | 429 | ```php 430 | // Result: 431 | 'Poly-Rectangle' 432 | ``` 433 | 434 | ### Enum::labels() 435 | 436 | Get labels for multiple enum cases. 437 | 438 | ```php 439 | $labels = Enum::labels(BackedEnumShape::class); 440 | ``` 441 | 442 | ```php 443 | // Result: 444 | ['Round Circle', 'Perfect Square', 'Right-Triangle', '5 Point Star', 'Poly-Rectangle'] 445 | ``` 446 | 447 | ### Enum::metadata() 448 | 449 | Retrieve the metadata attribute or specific metadata values by key(s). 450 | 451 | ```php 452 | $allMetadata = Enum::metadata(BackedEnumShape::BoxSquare); 453 | ``` 454 | 455 | ```php 456 | // Result: 457 | [ 458 | 'color' => 'blue', 459 | 'sides' => 4, 460 | 'type' => 'polygon', 461 | 'regular' => true 462 | ] 463 | ``` 464 | 465 | Get specific metadata value. 466 | 467 | ```php 468 | $specific = Enum::metadata(BackedEnumShape::BoxSquare, 'color'); 469 | ``` 470 | 471 | ```php 472 | // Result: 473 | 'blue' 474 | ``` 475 | 476 | Filter metadata by specific keys. 477 | 478 | ```php 479 | $filteredMetadata = Enum::metadata(BackedEnumShape::BoxSquare, ['color', 'type']); 480 | ``` 481 | 482 | ```php 483 | // Result: 484 | [ 485 | 'color' => 'blue', 486 | 'type' => 'polygon' 487 | ] 488 | ``` 489 | 490 | ### Enum::metadatum() 491 | 492 | Retrieve the metadata attribute for all cases, optionally filtered by key. 493 | 494 | ```php 495 | $allMetadatum = Enum::metadatum(BackedEnumShape::class); 496 | ``` 497 | 498 | ```php 499 | // Result: 500 | [ 501 | ['color' => 'red', 'sides' => 0, 'type' => 'curved'], 502 | ['color' => 'blue', 'sides' => 4, 'type' => 'polygon', 'regular' => true], 503 | ['color' => 'green', 'sides' => 3, 'type' => 'polygon'], 504 | ['color' => 'yellow', 'points' => 5, 'type' => 'star', 'symmetrical' => true], 505 | ['color' => 'purple', 'sides' => 4, 'type' => 'polygon', 'regular' => false] 506 | ] 507 | ``` 508 | 509 | Get specific metadatum values for the class. 510 | 511 | ```php 512 | $specific = Enum::metadatum(BackedEnumShape::class, 'color'); 513 | ``` 514 | 515 | ```php 516 | // Result: 517 | ['red', 'blue', 'green', 'yellow', 'purple'] 518 | ``` 519 | 520 | Use the `keyBy` argument to get an associative array using the case name or simple value as the keys/indices. 521 | 522 | ```php 523 | $keyBy = Enum::metadatum(BackedEnumShape::class, 'color', 'value'); 524 | ``` 525 | 526 | ```php 527 | // Result: 528 | [ 529 | 'circle' => 'red', 530 | 'square' => 'blue', 531 | 'triangle' => 'green', 532 | 'star' => 'yellow', 533 | 'rectangle' => 'purple' 534 | ] 535 | ``` 536 | 537 | Filter metadatum by specific keys. 538 | 539 | ```php 540 | $filteredMetadatum = Enum::metadatum(BackedEnumShape::class, ['color', 'sides']); 541 | ``` 542 | 543 | ```php 544 | // Result: 545 | [ 546 | ['color' => 'red', 'sides' => 0], 547 | ['color' => 'blue', 'sides' => 4], 548 | ['color' => 'green', 'sides' => 3], 549 | ['color' => 'yellow'], 550 | ['color' => 'purple', 'sides' => 4] 551 | ] 552 | ``` 553 | 554 | [top](#usage) 555 | 556 | ## Enum Traits (BackedEnum) 557 | 558 | The `BackedEnum` trait provides instance methods for working with backed enums. Apply this trait to your enum class to access these methods directly on enum instances. 559 | 560 | ```php 561 | use Iteks\Traits\BackedEnum; 562 | 563 | enum BackedEnumShape: string 564 | { 565 | use BackedEnum; 566 | ... 567 | ``` 568 | 569 | ### asSelectArray() 570 | 571 | Convert the enum to a select array format, useful for form select elements. 572 | 573 | ```php 574 | $options = BackedEnumShape::asSelectArray(); 575 | ``` 576 | 577 | ```php 578 | // Result: 579 | [ 580 | ['text' => 'Round Circle', 'value' => 1], 581 | ['text' => 'Perfect Square', 'value' => 2], 582 | ['text' => 'Right-Triangle', 'value' => 3], 583 | ['text' => '5 Point Star', 'value' => 4], 584 | ['text' => 'Poly-Rectangle', 'value' => 5] 585 | ] 586 | ``` 587 | 588 | ### fromName() 589 | 590 | Create an enum instance from a case name. 591 | 592 | ```php 593 | $enum = BackedEnumShape::fromName('RoundCircle'); 594 | ``` 595 | 596 | ```php 597 | // Result: 598 | // The enum instance of BackedEnumShape::RoundCircle 599 | { 600 | +name: "RoundCircle" 601 | +value: "circle" 602 | } 603 | ``` 604 | 605 | ### name() 606 | 607 | Get the case name of an enum instance. 608 | 609 | ```php 610 | $name = BackedEnumShape::name('square'); 611 | ``` 612 | 613 | ```php 614 | // Result: 615 | 'BoxSquare' 616 | ``` 617 | 618 | ### names() 619 | 620 | Get all case names from the enum. 621 | 622 | ```php 623 | $names = BackedEnumShape::names(); 624 | ``` 625 | 626 | ```php 627 | // Result: 628 | [ 629 | 'RoundCircle', 630 | 'BoxSquare', 631 | 'RightTriangle', 632 | 'PointedStar', 633 | 'PolygonRectangle' 634 | ] 635 | ``` 636 | 637 | ### toLabel() 638 | 639 | Convert an enum instance to its label representation. 640 | 641 | ```php 642 | $label = BackedEnumShape::toLabel('triangle'); 643 | ``` 644 | 645 | ```php 646 | // Result: 647 | 'Right-Triangle' 648 | ``` 649 | 650 | ### toLabels() 651 | 652 | Convert multiple enum instances to their label representations. 653 | 654 | ```php 655 | $labels = BackedEnumShape::toLabels(); 656 | ``` 657 | 658 | ```php 659 | // Result: 660 | [ 661 | 'Round Circle', 662 | 'Box Square', 663 | 'Right Triangle', 664 | 'Pointed Star', 665 | 'Polygon Rectangle' 666 | ] 667 | ``` 668 | 669 | ### tryFromName() 670 | 671 | Attempt to create an enum instance from a case name, returning null if the name doesn't exist. 672 | 673 | ```php 674 | $enum = BackedEnumShape::tryFromName('RoundCircle'); 675 | ``` 676 | 677 | ```php 678 | // Result: 679 | // The enum instance of BackedEnumShape::RoundCircle 680 | { 681 | +name: "RoundCircle" 682 | +value: "circle" 683 | } 684 | ``` 685 | 686 | ### value() 687 | 688 | Get the backed value of an enum instance. 689 | 690 | ```php 691 | $value = BackedEnumShape::value('RoundCircle'); 692 | ``` 693 | 694 | ```php 695 | // Result: 696 | 'circle' 697 | ``` 698 | 699 | ### values() 700 | 701 | Get all backed values from the enum. 702 | 703 | ```php 704 | $values = BackedEnumShape::values(); 705 | ``` 706 | 707 | ```php 708 | // Result: 709 | [ 710 | 'circle', 711 | 'square', 712 | 'triangle', 713 | 'star', 714 | 'rectangle' 715 | ] 716 | ``` 717 | 718 | [top](#usage) 719 | 720 | ## Enum Traits (HasAttributes) 721 | 722 | The `HasAttributes` trait provides instance methods for working with enum attributes. Apply this trait along with `BackedEnum` to access these methods directly on enum instances. 723 | 724 | ```php 725 | use Iteks\Traits\BackedEnum; 726 | use Iteks\Traits\HasAttributes; 727 | 728 | enum BackedEnumShape: string 729 | { 730 | use BackedEnum; 731 | use HasAttributes; 732 | ... 733 | ``` 734 | 735 | ### attributes() 736 | 737 | Get all attributes for an enum instance or all class instances. 738 | 739 | ```php 740 | $attributes = BackedEnumShape::attributes('RoundCircle'); 741 | ``` 742 | 743 | ```php 744 | // Result: 745 | [ 746 | 'simpleValue' => 'circle', 747 | 'description' => 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 748 | 'id' => 1, 749 | 'label' => 'Round Circle', 750 | 'metadata' => [ 751 | 'color' => 'red', 752 | 'sides' => 0, 753 | 'type' => 'curved' 754 | ] 755 | ] 756 | ``` 757 | 758 | Filter attributes for the enum instance. 759 | 760 | ```php 761 | $attributes = BackedEnumShape::attributes('RoundCircle', ['id', 'label']); 762 | 763 | // Result: 764 | [ 765 | 'id' => 1, 766 | 'label' => 'Round Circle' 767 | ] 768 | ``` 769 | 770 | Get all attributes for each instance in the class. 771 | 772 | ```php 773 | $attributes = BackedEnumShape::attributes(); 774 | ``` 775 | 776 | ```php 777 | // Result: 778 | [ 779 | 'RoundCircle' => [ 780 | 'simpleValue' => 'circle', 781 | 'description' => 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 782 | 'id' => 1, 783 | 'label' => 'Round Circle', 784 | 'metadata' => [, 785 | 'color' => 'red', 786 | 'sides' => 0, 787 | 'type' => 'curved' 788 | ] 789 | ], 790 | 'BoxSquare' => [ 791 | 'simpleValue' => 'square', 792 | 'description' => 'A square is a regular quadrilateral', 793 | 'id' => 2, 794 | 'label' => 'Perfect Square', 795 | 'metadata' => [ 796 | 'color' => 'blue', 797 | 'sides' => 4, 798 | 'type' => 'polygon', 799 | 'regular' => true 800 | ] 801 | ], 802 | 'RightTriangle' => [ 803 | 'simpleValue' => 'triangle', 804 | 'description' => 'A triangle is a polygon with three edges and three vertices', 805 | 'id' => 3, 806 | 'label' => 'Right-Triangle', 807 | 'metadata' => [, 808 | 'color' => 'green', 809 | 'sides' => 3, 810 | 'type' => 'polygon' 811 | ] 812 | ], 813 | ... 814 | ] 815 | ``` 816 | 817 | Filter all attributes for each instance in the class by key(s). 818 | 819 | ```php 820 | $attributes = BackedEnumShape::attributes(['description', 'label']); 821 | ``` 822 | 823 | ```php 824 | // Result: 825 | [ 826 | 'RoundCircle' => [ 827 | 'description' => 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 828 | 'label' => 'Round Circle' 829 | ], 830 | 'BoxSquare' => [ 831 | 'description' => 'A square is a regular quadrilateral', 832 | 'label' => 'Perfect Square' 833 | ], 834 | 'RightTriangle' => [ 835 | 'description' => 'A triangle is a polygon with three edges and three vertices', 836 | 'label' => 'Right-Triangle' 837 | ], 838 | ... 839 | ] 840 | ``` 841 | 842 | ### description() 843 | 844 | Get the description(s) for an enum instance or class. 845 | 846 | ```php 847 | $description = BackedEnumShape::description('RoundCircle'); 848 | ``` 849 | 850 | ```php 851 | // Result: 852 | 'A square is a regular quadrilateral, all sides have equal length and all angles are 90 degrees' 853 | ``` 854 | 855 | ### descriptions() 856 | 857 | Get descriptions for all enum cases. 858 | 859 | ```php 860 | $descriptions = BackedEnumShape::descriptions(); 861 | ``` 862 | 863 | ```php 864 | // Result: 865 | [ 866 | 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 867 | 'A square is a regular quadrilateral, all sides have equal length and all angles are 90 degrees', 868 | 'A triangle is a polygon with three edges and three vertices', 869 | 'A star is a self-intersecting, equilateral polygon', 870 | 'A rectangle is a quadrilateral with four right angles' 871 | ] 872 | ``` 873 | 874 | Use the `keyBy` argument to get an associative array using the case name or simple value as the keys/indices. 875 | 876 | ```php 877 | $descriptions = BackedEnumShape::descriptions('name'); 878 | 879 | // Result: 880 | [ 881 | 'RoundCircle' => 'A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center', 882 | 'BoxSquare' => 'A square is a regular quadrilateral', 883 | ... 884 | ] 885 | 886 | ``` 887 | 888 | ### id() 889 | 890 | Get the ID attribute for an enum instance. 891 | 892 | ```php 893 | $id = BackedEnumShape::id('PointedStar'); 894 | ``` 895 | 896 | ```php 897 | // Result: 898 | 4 899 | ``` 900 | 901 | ### ids() 902 | 903 | Get IDs for all enum cases. 904 | 905 | ```php 906 | $ids = BackedEnumShape::ids(); 907 | ``` 908 | 909 | ```php 910 | // Result: 911 | [1, 2, 3, 4, 5] 912 | ``` 913 | 914 | Use the `keyBy` argument to get an associative array using the case name or simple value as the keys/indices. 915 | 916 | ```php 917 | $ids = BackedEnumShape::ids('value'); 918 | ``` 919 | 920 | ```php 921 | // Result: 922 | ['circle' => 1, 'square' => 2, 'triangle' => 3, 'star' => 4, 'rectangle' => 5] 923 | ``` 924 | 925 | ### label() 926 | 927 | Get the label for an enum instance. 928 | 929 | ```php 930 | $label = BackedEnumShape::PolygonRectangle->label(); 931 | ``` 932 | 933 | ```php 934 | // Result: 935 | 'Poly-Rectangle' 936 | ``` 937 | 938 | ### labels() 939 | 940 | Get labels for all enum cases. 941 | 942 | ```php 943 | $labels = BackedEnumShape::labels(); 944 | ``` 945 | 946 | ```php 947 | // Result: 948 | ['Round Circle', 'Perfect Square', 'Right-Triangle', '5 Point Star', 'Poly-Rectangle'] 949 | ``` 950 | 951 | ### metadata() 952 | 953 | Retrieve the metadata attribute or specific metadata values by key(s). 954 | 955 | ```php 956 | $allMetadata = BackedEnumShape::metadata('BoxSquare'); 957 | ``` 958 | 959 | ```php 960 | // Result: 961 | [ 962 | 'color' => 'blue', 963 | 'sides' => 4, 964 | 'type' => 'polygon', 965 | 'regular' => true 966 | ] 967 | ``` 968 | 969 | Get specific metadata value. 970 | 971 | ```php 972 | $specific = BackedEnumShape::metadata('BoxSquare', 'color'); 973 | ``` 974 | 975 | ```php 976 | // Result: 977 | 'blue' 978 | ``` 979 | 980 | Filter metadata by specific keys. 981 | 982 | ```php 983 | $filteredMetadata = BackedEnumShape::metadata('BoxSquare', ['color', 'type']); 984 | ``` 985 | 986 | ```php 987 | // Result: 988 | [ 989 | 'color' => 'blue', 990 | 'type' => 'polygon' 991 | ] 992 | 993 | ``` 994 | 995 | ### metadatum() 996 | 997 | Retrieve the metadata attribute for all cases, optionally filtered by key. 998 | 999 | ```php 1000 | $allMetadatum = BackedEnumShape::metadatum(); 1001 | ``` 1002 | 1003 | ```php 1004 | // Result: 1005 | [ 1006 | ['color' => 'red', 'sides' => 0, 'type' => 'curved'], 1007 | ['color' => 'blue', 'sides' => 4, 'type' => 'polygon', 'regular' => true], 1008 | ['color' => 'green', 'sides' => 3, 'type' => 'polygon'], 1009 | ['color' => 'yellow', 'points' => 5, 'type' => 'star', 'symmetrical' => true], 1010 | ['color' => 'purple', 'sides' => 4, 'type' => 'polygon', 'regular' => false] 1011 | ] 1012 | ``` 1013 | 1014 | Get specific metadatum values only for the class. 1015 | 1016 | ```php 1017 | $specific = BackedEnumShape::metadatum('color'); 1018 | ``` 1019 | 1020 | ```php 1021 | // Result: 1022 | ['red', 'blue', 'green', 'yellow', 'purple'] 1023 | ``` 1024 | 1025 | Use the `keyBy` argument to get an associative array using the case name or simple value as the keys/indices. 1026 | 1027 | ```php 1028 | $keyBy = BackedEnumShape::metadatum('color', 'value'); 1029 | ``` 1030 | 1031 | ```php 1032 | // Result: 1033 | [ 1034 | 'circle' => 'red', 1035 | 'square' => 'blue', 1036 | 'triangle' => 'green', 1037 | 'star' => 'yellow', 1038 | 'rectangle' => 'purple' 1039 | ] 1040 | ``` 1041 | 1042 | Filter metadatum by specific keys. 1043 | 1044 | ```php 1045 | $filteredMetadatum = BackedEnumShape::metadatum(['color', 'sides']); 1046 | 1047 | // Result: 1048 | [ 1049 | ['color' => 'red', 'sides' => 0], 1050 | ['color' => 'blue', 'sides' => 4], 1051 | ['color' => 'green', 'sides' => 3], 1052 | ['color' => 'yellow'], 1053 | ['color' => 'purple', 'sides' => 4] 1054 | ] 1055 | ``` 1056 | 1057 | ### meta() 1058 | 1059 | Access metadata values using property-like syntax. This is the recommended way to access individual metadata values. 1060 | 1061 | ```php 1062 | // Access metadata values directly 1063 | $color = BackedEnumShape::RoundCircle->meta()->color; // 'red' 1064 | $sides = BackedEnumShape::RoundCircle->meta()->sides; // 0 1065 | $type = BackedEnumShape::RoundCircle->meta()->type; // 'curved' 1066 | 1067 | // Safely access non-existent properties 1068 | $unknown = BackedEnumShape::RoundCircle->meta()->nonexistent; // null 1069 | 1070 | // Access nested JSON string metadata 1071 | $regular = BackedEnumShape::BoxSquare->meta()->regular; // true 1072 | ``` 1073 | 1074 | [top](#usage) 1075 | 1076 | ## String Helper Macros 1077 | 1078 | The **Laravel Enum** package extends Laravel's `Str` facade with additional helper methods for working with enum case names. 1079 | 1080 | ### Str::splitConstantCase() 1081 | 1082 | Split a constant case string into its constituent words. This is useful when working with enum case names that follow constant case naming conventions (a.k.a. Snake case). 1083 | 1084 | ```php 1085 | use Illuminate\Support\Str; 1086 | 1087 | // Split a Snake case (ALL CAPS) string 1088 | $words = Str::splitConstantCase('CONSTANT_CASE'); // 'CONSTANT CASE' 1089 | // Split a Snake case (Title) string 1090 | $words = Str::splitConstantCase('Constant_Case'); // 'Constant Case' 1091 | // Split a Snake case (all lowercase) string 1092 | $words = Str::splitConstantCase('constant_case'); // 'constant case' 1093 | ``` 1094 | 1095 | ### Str::splitEnumCase() 1096 | 1097 | Split an enum case name into its constituent words. This is useful when working with enum case names that follow enum case (a.k.a. Pascal case) naming conventions. Also, handles Camel case strings. 1098 | 1099 | ```php 1100 | use Illuminate\Support\Str; 1101 | 1102 | // Split an Pascal case string 1103 | $words = Str::splitEnumCase('EnumCase'); // 'Enum Case' 1104 | // Split an Camel case string 1105 | $words = Str::splitEnumCase('enumCase'); // 'enum Case' 1106 | ``` 1107 | 1108 | These string helper macros are particularly useful when you need to: 1109 | 1110 | - Generate human-readable labels from enum case names 1111 | - Parse enum case names for display or processing 1112 | - Create consistent formatting across your application 1113 | 1114 | [top](#usage) 1115 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iteks/laravel-enum", 3 | "description": "A comprehensive Laravel package providing enhanced enum functionalities, including attribute handling, select array conversions, and fluent facade interactions for robust enum management in Laravel applications.", 4 | "type": "library", 5 | "keywords": ["laravel","php", "enum", "attribute", "trait", "helper", "collection", "array", "string", "data", "serialize"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Jeramy Hing", 10 | "email": "iteks@msn.com" 11 | } 12 | ], 13 | "require": { 14 | "php": "^8.1", 15 | "laravel/framework": "^9.46|^10.10|^11.0|^12.0" 16 | }, 17 | "require-dev": { 18 | "laravel/pint": "^1.14", 19 | "pestphp/pest": "^2.34", 20 | "pestphp/pest-plugin-arch": "^2.7", 21 | "phpstan/phpstan": "^1.10", 22 | "symfony/var-dumper": "^6.4" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Iteks\\Attributes\\": "src/Attributes/", 27 | "Iteks\\Support\\": "src/", 28 | "Iteks\\Traits\\": "src/Traits/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Tests\\": "tests" 34 | } 35 | }, 36 | "scripts": { 37 | "pint": "pint -v --test", 38 | "pint:fix": "pint -v", 39 | "phpstan": [ 40 | "phpstan analyse --ansi" 41 | ], 42 | "test": "pest --colors=always", 43 | "test:all": [ 44 | "@pint:lint", 45 | "@phpstan", 46 | "@test" 47 | ] 48 | }, 49 | "config": { 50 | "optimize-autoloader": true, 51 | "preferred-install": "dist", 52 | "sort-packages": true, 53 | "allow-plugins": { 54 | "pestphp/pest-plugin": true, 55 | "php-http/discovery": true 56 | } 57 | }, 58 | "extra": { 59 | "laravel": { 60 | "providers": [ 61 | "Iteks\\Support\\EnumServiceProvider" 62 | ] 63 | } 64 | }, 65 | "minimum-stability": "dev", 66 | "prefer-stable": true 67 | } 68 | -------------------------------------------------------------------------------- /dev/examples/backed-enum.php: -------------------------------------------------------------------------------- 1 | '; 8 | echo 'Enum Helpers (BackedEnum)'; 9 | 10 | echo '
';
11 | $options = Enum::asSelectArray(BackedEnumShape::class);
12 | echo '$options = Enum::asSelectArray(BackedEnumShape::class);';
13 | dump($options);
14 | 
15 | $label = Enum::toLabel(BackedEnumShape::RoundCircle);
16 | echo '$label = Enum::toLabel(BackedEnumShape::RoundCircle);';
17 | dump($label);
18 | 
19 | $labels = Enum::toLabels(BackedEnumShape::class);
20 | echo '$labels = Enum::toLabels(BackedEnumShape::class);';
21 | dump($labels);
22 | echo '
'; 23 | echo ''; 24 | 25 | // Enum Traits (BackedEnum) 26 | echo '
'; 27 | echo 'Enum Traits (BackedEnum)'; 28 | 29 | echo '
';
30 | $options = BackedEnumShape::asSelectArray();
31 | echo '$options = BackedEnumShape::asSelectArray();';
32 | dump($options);
33 | 
34 | $enum = BackedEnumShape::fromName('RoundCircle');
35 | echo '$enum = BackedEnumShape::fromName(\'RoundCircle\');';
36 | dump($enum);
37 | 
38 | $name = BackedEnumShape::name('circle');
39 | // $name = BackedEnumShape::RoundCircle->name();
40 | echo '$name = BackedEnumShape::name(\'circle\');';
41 | dump($name);
42 | 
43 | $names = BackedEnumShape::names();
44 | echo '$names = BackedEnumShape::names();';
45 | dump($names);
46 | 
47 | $label = BackedEnumShape::toLabel('circle');
48 | // $label = BackedEnumShape::RoundCircle->toLabel();
49 | echo '$label = BackedEnumShape::toLabel(\'circle\');';
50 | dump($label);
51 | 
52 | $labels = BackedEnumShape::toLabels();
53 | echo '$labels = BackedEnumShape::toLabels();';
54 | dump($labels);
55 | 
56 | $enum = BackedEnumShape::tryFromName('RoundCircle');
57 | echo '$enum = BackedEnumShape::tryFromName(\'RoundCircle\');';
58 | dump($enum);
59 | 
60 | $simplerValue = BackedEnumShape::value('RoundCircle');
61 | echo '$simplerValue = BackedEnumShape::value(\'RoundCircle\');';
62 | dump($simplerValue);
63 | 
64 | $simplerValues = BackedEnumShape::values();
65 | echo '$simplerValues = BackedEnumShape::values();';
66 | dump($simplerValues);
67 | echo '
'; 68 | echo '
'; 69 | -------------------------------------------------------------------------------- /dev/examples/has-attributes.php: -------------------------------------------------------------------------------- 1 | '; 8 | echo 'Enum Helpers (HasAttributes)'; 9 | 10 | echo '
';
 11 | $attributes = Enum::attributes(BackedEnumShape::RoundCircle);
 12 | echo '$attributes = Enum::attributes(BackedEnumShape::RoundCircle);';
 13 | dump($attributes);
 14 | 
 15 | $attributes = Enum::attributes(BackedEnumShape::RoundCircle, ['id', 'label']);
 16 | echo '$attributes = Enum::attributes(BackedEnumShape::RoundCircle, [\'id\', \'label\']);';
 17 | dump($attributes);
 18 | 
 19 | $attributes = Enum::attributes(BackedEnumShape::class);
 20 | echo '$attributes = Enum::attributes(BackedEnumShape::class);';
 21 | dump($attributes);
 22 | 
 23 | $attributes = Enum::attributes(BackedEnumShape::class, ['label', 'description']);
 24 | echo '$attributes = Enum::attributes(BackedEnumShape::class, [\'label\', \'description\']);';
 25 | dump($attributes);
 26 | 
 27 | $description = Enum::description(BackedEnumShape::RoundCircle);
 28 | echo '$description = Enum::description(BackedEnumShape::RoundCircle);';
 29 | dump($description);
 30 | 
 31 | $descriptions = Enum::descriptions(BackedEnumShape::class);
 32 | echo '$descriptions = Enum::descriptions(BackedEnumShape::class);';
 33 | dump($descriptions);
 34 | 
 35 | $descriptions = Enum::descriptions(BackedEnumShape::class, 'name');
 36 | echo '$descriptions = Enum::descriptions(BackedEnumShape::class, \'name\');';
 37 | dump($descriptions);
 38 | 
 39 | $id = Enum::id(BackedEnumShape::RoundCircle);
 40 | echo '$id = Enum::id(BackedEnumShape::RoundCircle);';
 41 | dump($id);
 42 | 
 43 | $ids = Enum::ids(BackedEnumShape::class);
 44 | echo '$ids = Enum::ids(BackedEnumShape::class);';
 45 | dump($ids);
 46 | 
 47 | $ids = Enum::ids(BackedEnumShape::class, 'value');
 48 | echo '$ids = Enum::ids(BackedEnumShape::class, \'name\');';
 49 | dump($ids);
 50 | 
 51 | $label = Enum::label(BackedEnumShape::RoundCircle);
 52 | echo '$label = Enum::label(BackedEnumShape::RoundCircle);';
 53 | dump($label);
 54 | 
 55 | $labels = Enum::labels(BackedEnumShape::class);
 56 | echo '$labels = Enum::labels(BackedEnumShape::class);';
 57 | dump($labels);
 58 | 
 59 | $labels = Enum::labels(BackedEnumShape::class, 'name');
 60 | echo '$labels = Enum::labels(BackedEnumShape::class, \'name\');';
 61 | dump($labels);
 62 | 
 63 | $metadata = Enum::metadata(BackedEnumShape::RoundCircle);
 64 | echo '$metadata = Enum::metadata(BackedEnumShape::RoundCircle);';
 65 | dump($metadata);
 66 | 
 67 | $metadatum = Enum::metadatum(BackedEnumShape::class);
 68 | echo '$metadatum = Enum::metadatum(BackedEnumShape::class);';
 69 | dump($metadatum);
 70 | 
 71 | $metadatum = Enum::metadatum(BackedEnumShape::class, 'name');
 72 | echo '$metadatum = Enum::metadatum(BackedEnumShape::class, \'name\');';
 73 | dump($metadatum);
 74 | echo '
'; 75 | echo ''; 76 | 77 | // Enum Traits (HasAttributes) 78 | echo '
'; 79 | echo 'Enum Traits (HasAttributes)'; 80 | 81 | echo '
';
 82 | $attributes = BackedEnumShape::attributes('RoundCircle');
 83 | echo '$attributes = BackedEnumShape::attributes(\'RoundCircle\');';
 84 | dump($attributes);
 85 | 
 86 | $attributes = BackedEnumShape::attributes('RoundCircle', ['id', 'label']);
 87 | echo '$attributes = BackedEnumShape::attributes(\'RoundCircle\', [\'id\', \'label\']);';
 88 | dump($attributes);
 89 | 
 90 | $attributes = BackedEnumShape::attributes();
 91 | echo '$attributes = BackedEnumShape::attributes();';
 92 | dump($attributes);
 93 | 
 94 | $attributes = BackedEnumShape::attributes(null, ['description', 'metadata']);
 95 | echo '$attributes = BackedEnumShape::attributes(null, [\'description\', \'metadata\']);';
 96 | dump($attributes);
 97 | 
 98 | $description = BackedEnumShape::description('RoundCircle');
 99 | // $description = BackedEnumShape::RoundCircle->description();
100 | echo '$description = BackedEnumShape::description(\'RoundCircle\');';
101 | dump($description);
102 | 
103 | $descriptions = BackedEnumShape::descriptions();
104 | echo '$descriptions = BackedEnumShape::descriptions();';
105 | dump($descriptions);
106 | 
107 | $descriptions = BackedEnumShape::descriptions('name');
108 | echo '$descriptions = BackedEnumShape::descriptions(\'name\');';
109 | dump($descriptions);
110 | 
111 | $id = BackedEnumShape::id('PointedStar');
112 | // $id = BackedEnumShape::PointedStar->id();
113 | echo '$id = BackedEnumShape::id(\'PointedStar\');';
114 | dump($id);
115 | 
116 | $ids = BackedEnumShape::ids();
117 | echo '$ids = BackedEnumShape::ids();';
118 | dump($ids);
119 | 
120 | $ids = BackedEnumShape::ids('name');
121 | echo '$ids = BackedEnumShape::ids(\'name\');';
122 | dump($ids);
123 | 
124 | $label = BackedEnumShape::label('PolygonRectangle');
125 | // $label = BackedEnumShape::PolygonRectangle->label();
126 | echo '$label = BackedEnumShape::label(\'PolygonRectangle\');';
127 | dump($label);
128 | 
129 | $labels = BackedEnumShape::labels();
130 | echo '$labels = BackedEnumShape::labels();';
131 | dump($labels);
132 | 
133 | $labels = BackedEnumShape::labels('name');
134 | echo '$labels = BackedEnumShape::labels(\'name\');';
135 | dump($labels);
136 | 
137 | $metadata = BackedEnumShape::metadata('RoundCircle');
138 | // $metadata = BackedEnumShape::RoundCircle->metadata();
139 | echo '$metadata = BackedEnumShape::metadata(\'RoundCircle\');';
140 | dump($metadata);
141 | 
142 | $metadatum = BackedEnumShape::metadatum();
143 | echo '$metadatum = BackedEnumShape::metadatum();';
144 | dump($metadatum);
145 | echo '
'; 146 | echo '
'; 147 | 148 | echo '
'; 149 | echo 'NEW Metadata Accessors'; 150 | 151 | // NEW Metadata Options 152 | echo '

Accessing metadata through helper and trait methods:

'; 153 | 154 | echo '
';
155 | echo '// All metadata
'; 156 | echo 'Enum::metadata(BackedEnumShape::BoxSquare); // [\'color\' => \'blue\', \'sides\' => 4, \'type\' => \'polygon\', \'regular\' => true]
'; 157 | echo '// Single key
'; 158 | echo 'Enum::metadata(BackedEnumShape::BoxSquare, \'color\'); // \'blue\'
'; 159 | echo '// Multiple keys
'; 160 | echo 'Enum::metadata(BackedEnumShape::BoxSquare, [\'color\', \'sides\']); // [\'color\' => \'blue\', \'sides\' => 4]
'; 161 | dump([ 162 | 'All metadata' => Enum::metadata(BackedEnumShape::BoxSquare), 163 | 'Single key' => Enum::metadata(BackedEnumShape::BoxSquare, 'color'), 164 | 'Multiple keys' => Enum::metadata(BackedEnumShape::BoxSquare, ['color', 'sides']), 165 | ]); 166 | 167 | echo '// All metadatum
'; 168 | echo 'Enum::metadatum(BackedEnumShape::class); // [[\'color\' => \'red\', \'sides\' => 0, \'type\' => \'curved\'], ...each case metadata]
'; 169 | echo '// Single key
'; 170 | echo 'Enum::metadatum(BackedEnumShape::class, \'color\'); // [\'red\', \'blue\', \'green\', \'yellow\', \'purple\']
'; 171 | echo '// Multiple keys
'; 172 | echo 'Enum::metadatum(BackedEnumShape::class, [\'color\', \'sides\']); // [[\'color\' => \'red\', \'sides\' => 0], ...each case metadata]
'; 173 | dump([ 174 | 'All metadatum' => Enum::metadatum(BackedEnumShape::class), 175 | 'Single key' => Enum::metadatum(BackedEnumShape::class, 'color'), 176 | 'Multiple keys' => Enum::metadatum(BackedEnumShape::class, ['color', 'sides'], 'value'), 177 | ]); 178 | 179 | echo '// All metadata
'; 180 | echo 'BackedEnumShape::metadata(\'BoxSquare\'); // [\'color\' => \'blue\', \'sides\' => 4, \'type\' => \'polygon\', \'regular\' => true]
'; 181 | echo '// Single key
'; 182 | echo 'BackedEnumShape::metadata(\'BoxSquare\', \'color\'); // \'blue\'
'; 183 | echo '// Multiple keys
'; 184 | echo 'BackedEnumShape::metadata(\'BoxSquare\', [\'color\', \'sides\']); // [\'color\' => \'blue\', \'sides\' => 4]
'; 185 | dump([ 186 | 'All metadata' => BackedEnumShape::metadata('BoxSquare'), 187 | 'Single key' => BackedEnumShape::metadata('BoxSquare', 'color'), 188 | 'Multiple keys' => BackedEnumShape::metadata('BoxSquare', ['color', 'sides']), 189 | ]); 190 | 191 | echo '// All metadatum
'; 192 | echo 'BackedEnumShape::metadatum(); // [[\'color\' => \'red\', \'sides\' => 0, \'type\' => \'curved\'], ...each case metadata]
'; 193 | echo '// Single key
'; 194 | echo 'BackedEnumShape::metadatum(\'color\'); // [\'red\', \'blue\', \'green\', \'yellow\', \'purple\']
'; 195 | echo '// Multiple keys
'; 196 | echo 'BackedEnumShape::metadatum([\'color\', \'sides\']); // [[\'color\' => \'red\', \'sides\' => 0], ...each case metadata]
'; 197 | dump([ 198 | 'All metadatum' => BackedEnumShape::metadatum(), 199 | 'Single key' => BackedEnumShape::metadatum('color', 'value'), 200 | 'Multiple keys' => BackedEnumShape::metadatum(['color', 'sides'], 'name'), 201 | ]); 202 | echo '
'; 203 | 204 | // NEW Metadata Trait Accessors 205 | echo '

Accessing metadata through meta() property accessor:

'; 206 | 207 | echo '
';
208 | echo 'BackedEnumShape::RoundCircle->meta()->color;
'; 209 | echo 'BackedEnumShape::RoundCircle->meta()->type;
'; 210 | echo 'BackedEnumShape::BoxSquare->meta()->color;
'; 211 | echo 'BackedEnumShape::BoxSquare->meta()->sides;
'; 212 | dump([ 213 | 'RoundCircle->color' => BackedEnumShape::RoundCircle->meta()->color, 214 | 'RoundCircle->type' => BackedEnumShape::RoundCircle->meta()->type, 215 | 'BoxSquare->color' => BackedEnumShape::BoxSquare->meta()->color, 216 | 'BoxSquare->sides' => BackedEnumShape::BoxSquare->meta()->sides, 217 | ]); 218 | 219 | echo 'BackedEnumShape::BoxSquare->meta()->nonexistent;
'; 220 | dump(BackedEnumShape::BoxSquare->meta()->nonexistent); 221 | echo '
'; 222 | echo '
'; 223 | -------------------------------------------------------------------------------- /dev/examples/str-helpers.php: -------------------------------------------------------------------------------- 1 | '; 7 | echo 'Str Helper Macros'; 8 | 9 | echo '
';
10 | $string = Str::splitConstantCase('CONSTANT_CASE');
11 | echo '$string = Str::splitConstantCase(\'CONSTANT_CASE\');';
12 | dump($string);
13 | 
14 | $string = Str::splitEnumCase('EnumCase');
15 | echo '$string = Str::splitEnumCase(\'EnumCase\');';
16 | dump($string);
17 | echo '
'; 18 | echo ''; 19 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | #includes: 2 | # - vendor/larastan/larastan/extension.neon 3 | 4 | parameters: 5 | 6 | paths: 7 | - src/ 8 | 9 | excludePaths: 10 | # - ./*/*/FileToBeExcluded.php 11 | - tests/ 12 | - vendor/ 13 | 14 | # Level 9 is the highest level. See: https://phpstan.org/user-guide/rule-levels 15 | level: 8 16 | 17 | ignoreErrors: 18 | # - '#PHPDoc tag @var#' 19 | # These arre resolved by setting `checkMissingIterableValueType: false` below. 20 | # - '#Method [a-zA-Z0-9_\\:()]+ return type has no value type specified in iterable type array#' 21 | # - '#Method [a-zA-Z0-9_\\:()]+ has parameter \$[a-zA-Z0-9_]+ with no value type specified in iterable type array#' 22 | - '#Call to an undefined static method Illuminate\\Support\\Str\:\:splitConstantCase\(\)#' 23 | - '#Call to an undefined static method Illuminate\\Support\\Str\:\:splitEnumCase\(\)#' 24 | 25 | checkMissingIterableValueType: false -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 26 | ./tests 27 | 28 | 29 | 30 | 31 | ./src 32 | 33 | 34 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "concat_space": false, 5 | "no_superfluous_phpdoc_tags": false, 6 | "phpdoc_separation": { 7 | "groups": [ 8 | ["deprecated", "link", "see", "since"], 9 | ["author", "copyright", "license"], 10 | ["category", "package", "subpackage"], 11 | ["property", "property-read", "property-write"], 12 | ["param", "return"], 13 | ["method", "memberof"] 14 | ] 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Attributes/Description.php: -------------------------------------------------------------------------------- 1 | app->singleton('enum', function () { 21 | return new EnumService(new BackedEnumService, new HasAttributesService); 22 | }); 23 | 24 | // $this->app->alias('enum', EnumService::class); 25 | // $this->app->alias('enum', Enum::class); 26 | } 27 | 28 | public function boot(): void 29 | { 30 | Str::macro('splitConstantCase', function ($value) { 31 | return StrMacro::splitConstantCase($value); 32 | }); 33 | 34 | Str::macro('splitEnumCase', function ($value) { 35 | return StrMacro::splitEnumCase($value); 36 | }); 37 | } 38 | 39 | public function provides(): array 40 | { 41 | return [ 42 | 'enum', 43 | EnumService::class, 44 | BackedEnumService::class, 45 | HasAttributesService::class, 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Enums/BackedEnumShape.php: -------------------------------------------------------------------------------- 1 | '1.0', 'category' => 'geometry'])] 14 | enum BackedEnumShape: string 15 | { 16 | use BackedEnum; 17 | use HasAttributes; 18 | 19 | #[Description('A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center')] 20 | #[Id(1)] 21 | #[Label('Round Circle')] 22 | #[Metadata(['color' => 'red', 'sides' => 0, 'type' => 'curved'])] 23 | case RoundCircle = 'circle'; 24 | 25 | #[Description('A square is a regular quadrilateral, all sides have equal length and all angles are 90 degrees')] 26 | #[Id(2)] 27 | #[Label('Perfect Square')] 28 | #[Metadata('{"color": "blue", "sides": 4, "type": "polygon", "regular": true}')] 29 | case BoxSquare = 'square'; 30 | 31 | #[Description('A triangle is a polygon with three edges and three vertices')] 32 | #[Id(3)] 33 | #[Label('Right-Triangle')] 34 | #[Metadata(['color' => 'green', 'sides' => 3, 'type' => 'polygon'])] 35 | case RightTriangle = 'triangle'; 36 | 37 | #[Description('A star is a self-intersecting, equilateral polygon')] 38 | #[Id(4)] 39 | #[Label('5 Point Star')] 40 | #[Metadata('{"color": "yellow", "points": 5, "type": "star", "symmetrical": true}')] 41 | case PointedStar = 'star'; 42 | 43 | #[Description('A rectangle is a quadrilateral with four right angles')] 44 | #[Id(5)] 45 | #[Label('Poly-Rectangle')] 46 | #[Metadata(['color' => 'purple', 'sides' => 4, 'type' => 'polygon', 'regular' => false])] 47 | case PolygonRectangle = 'rectangle'; 48 | } 49 | -------------------------------------------------------------------------------- /src/Enums/BackedEnumStatus.php: -------------------------------------------------------------------------------- 1 | 'positive', 'display_color' => 'green'])] 21 | case CurrentlyActive = 1; 22 | 23 | #[Description('Pending status indicating the resource is awaiting processing or approval')] 24 | #[Id(102)] 25 | #[Label('Pending')] 26 | #[Metadata('{"status_type": "neutral", "display_color": "yellow"}')] 27 | case PendingReview = 2; 28 | 29 | #[Description('Temporarily suspended status indicating the resource is on hold')] 30 | #[Id(103)] 31 | #[Label('Suspended (!)')] 32 | #[Metadata([])] 33 | case TemporarilySuspended = 3; 34 | } 35 | -------------------------------------------------------------------------------- /src/Facades/Enum.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | public static function asSelectArray(string $enum): array 18 | { 19 | return array_map(function ($case) use ($enum): array { 20 | // If the enum class has a label and id method, use them to get the text and value. 21 | if (method_exists($enum, 'label') && method_exists($enum, 'id')) { 22 | return [ 23 | 'text' => HasAttributesService::label($case) ?? self::toLabel($case), 24 | 'value' => HasAttributesService::id($case) ?? $case->value, 25 | ]; 26 | } 27 | 28 | return [ 29 | 'text' => self::toLabel($case), 30 | 'value' => $case->value, 31 | ]; 32 | }, $enum::cases()); 33 | } 34 | 35 | /** 36 | * Create a label from the case name. 37 | * 38 | * @param mixed $case The case instance. 39 | * @param bool $isConst Case format is a traditional "CONSTANT_CASE" string. 40 | * @return string The created label. 41 | */ 42 | public static function toLabel(mixed $case, bool $isConst = false): string 43 | { 44 | $name = $case ? $case->name : ''; 45 | $name = $isConst ? Str::splitConstantCase($name) : Str::splitEnumCase($name); 46 | 47 | return $name ? Str::title($name) : ''; 48 | } 49 | 50 | /** 51 | * Create and compile an array of labels from the case names. 52 | * 53 | * @param string $enum The enum class. 54 | * @param bool $isConst Case format is a traditional "CONSTANT_CASE" string. 55 | * @return array The created labels. 56 | */ 57 | public static function toLabels(string $enum, bool $isConst = false): array 58 | { 59 | return array_map(function ($name) use ($isConst): string { 60 | return Str::title($isConst ? Str::splitConstantCase($name) : Str::splitEnumCase($name)); 61 | }, array_column($enum::cases(), 'name')); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Services/EnumService.php: -------------------------------------------------------------------------------- 1 | backedEnum, $method)) { 36 | return $this->backedEnum->$method(...$parameters); 37 | } elseif (method_exists($this->hasAttributes, $method)) { 38 | return $this->hasAttributes->$method(...$parameters); 39 | } 40 | 41 | throw new BadMethodCallException('Call to undefined method Iteks\Support\Facades\Enum::' . $method . '()'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Services/HasAttributesService.php: -------------------------------------------------------------------------------- 1 | 19 | */ 20 | public static function attributes(mixed $enumOrCase, ?array $filter = null): array 21 | { 22 | if (is_object($enumOrCase)) { 23 | return self::getAttributes($enumOrCase, $filter); 24 | } 25 | 26 | $attributes = []; 27 | foreach ($enumOrCase::cases() as $case) { 28 | $attributes[$case->name] = self::getAttributes($case, $filter); 29 | } 30 | 31 | return $attributes; 32 | } 33 | 34 | /** 35 | * Retrieve the description attribute. 36 | * 37 | * @param mixed $case The enum case instance. 38 | * @return string|null 39 | */ 40 | public static function description(mixed $case): ?string 41 | { 42 | return self::tryAttributeClass(Description::class, $case)?->description; 43 | } 44 | 45 | /** 46 | * Retrieve the description attribute for all cases. 47 | * 48 | * @param string $enum The enum class instance. 49 | * @param 'name'|'value'|null $keyBy Whether to key the result by case name or case value. Defaults to zero-indexed array. 50 | * @return array 51 | */ 52 | public static function descriptions(string $enum, ?string $keyBy = null): array 53 | { 54 | return self::mapCasesTo($enum, fn ($case) => self::description($case), $keyBy); 55 | } 56 | 57 | /** 58 | * Retrieve the id attribute. 59 | * 60 | * @param mixed $case The enum case instance. 61 | * @return int|string|null 62 | */ 63 | public static function id(mixed $case): int|string|null 64 | { 65 | return self::tryAttributeClass(Id::class, $case)?->id; 66 | } 67 | 68 | /** 69 | * Retrieve the id attribute for all cases. 70 | * 71 | * @param string $enum The enum class instance. 72 | * @param 'name'|'value'|null $keyBy Whether to key the result by case name or case value. Defaults to zero-indexed array. 73 | * @return array 74 | */ 75 | public static function ids(string $enum, ?string $keyBy = null): array 76 | { 77 | return self::mapCasesTo($enum, fn ($case) => self::id($case), $keyBy); 78 | } 79 | 80 | /** 81 | * Retrieve the label attribute. 82 | * 83 | * @param mixed $case The enum case instance. 84 | * @return string|null 85 | */ 86 | public static function label(mixed $case): ?string 87 | { 88 | return self::tryAttributeClass(Label::class, $case)?->label; 89 | } 90 | 91 | /** 92 | * Retrieve the label attribute for all cases. 93 | * 94 | * @param string $enum The enum class instance. 95 | * @param 'name'|'value'|null $keyBy Whether to key the result by case name or case value. Defaults to zero-indexed array. 96 | * @return array 97 | */ 98 | public static function labels(string $enum, ?string $keyBy = null): array 99 | { 100 | return self::mapCasesTo($enum, fn ($case) => self::label($case), $keyBy); 101 | } 102 | 103 | /** 104 | * Retrieve the metadata attribute or specific metadata values by key(s). 105 | * 106 | * @param mixed $case The enum case instance. 107 | * @param string|array|null $key Optional key or array of keys to retrieve specific metadata values. 108 | * @return mixed 109 | */ 110 | public static function metadata(mixed $case, string|array|null $key = null): mixed 111 | { 112 | $metadata = self::tryAttributeClass(Metadata::class, $case)?->metadata; 113 | 114 | // Handle JSON string metadata 115 | if (is_string($metadata) && str_starts_with(trim($metadata), '{')) { 116 | $decoded = json_decode($metadata, true); 117 | if (json_last_error() === JSON_ERROR_NONE) { 118 | $metadata = $decoded; 119 | } 120 | } 121 | 122 | if ($key === null || ! is_array($metadata)) { 123 | return $metadata; 124 | } 125 | 126 | if (is_array($key)) { 127 | return array_intersect_key($metadata, array_flip($key)); 128 | } 129 | 130 | return $metadata[$key] ?? null; 131 | } 132 | 133 | /** 134 | * Retrieve the metadata attribute for all cases, optionally filtered by metadata key(s). 135 | * 136 | * @param string $enum The enum class instance. 137 | * @param string|array|null $key Optional key or array of keys to retrieve specific metadata values. 138 | * @param 'name'|'value'|null $keyBy Whether to key the result by case name or case value. Defaults to zero-indexed array. 139 | * @return array 140 | */ 141 | public static function metadatum(string $enum, string|array|null $key = null, ?string $keyBy = null): array 142 | { 143 | return self::mapCasesTo($enum, fn ($case) => self::metadata($case, $key), $keyBy); 144 | } 145 | 146 | /** 147 | * Try to get an attribute class instance. 148 | * 149 | * @param string $attribute The attribute class. 150 | * @param mixed $case The case instance. 151 | * @return mixed 152 | */ 153 | public static function tryAttributeClass(string $attribute, mixed $case): mixed 154 | { 155 | $ref = new ReflectionClassConstant($case::class, $case->name); 156 | $classAttributes = $ref->getAttributes($attribute); 157 | 158 | if (count($classAttributes) === 0) { 159 | return null; 160 | } 161 | 162 | return $classAttributes[0]->newInstance(); 163 | } 164 | 165 | /** 166 | * Retrieve all of the attributes for the given enum class. Optionally, filter the attributes. 167 | * 168 | * @param mixed $enum The enum instance. 169 | * @param array|null $filter Filter attributes array. 170 | * @return array 171 | */ 172 | protected static function getAttributes(mixed $enum, ?array $filter = null): array 173 | { 174 | return $filter ? array_filter([ 175 | 'simpleValue' => $enum->value, 176 | 'description' => self::description($enum), 177 | 'id' => self::id($enum), 178 | 'label' => self::label($enum), 179 | 'metadata' => self::metadata($enum), 180 | ], function ($key) use ($filter) { 181 | return in_array($key, $filter); 182 | }, ARRAY_FILTER_USE_KEY) : [ 183 | 'simpleValue' => $enum->value, 184 | 'description' => self::description($enum), 185 | 'id' => self::id($enum), 186 | 'label' => self::label($enum), 187 | 'metadata' => self::metadata($enum), 188 | ]; 189 | } 190 | 191 | /** 192 | * Map enum cases to a given callback, optionally keyed by 'name' or 'value'. 193 | * 194 | * @template T 195 | * 196 | * @param mixed $enum The enum instance. 197 | * @param callable(mixed): T $callback 198 | * @param 'name'|'value'|null $keyBy 199 | * @return array 200 | */ 201 | protected static function mapCasesTo(mixed $enum, callable $callback, ?string $keyBy = null): array 202 | { 203 | $cases = $enum::cases(); 204 | 205 | if ($keyBy === null) { 206 | return array_map( 207 | fn ($case) => $callback($case), 208 | $cases 209 | ); 210 | } 211 | 212 | $keys = match ($keyBy) { 213 | 'value' => array_column($cases, 'value'), 214 | default => array_column($cases, 'name'), 215 | }; 216 | 217 | return array_map( 218 | fn ($case) => $callback($case), 219 | array_combine($keys, $cases) 220 | ); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/Services/MetadataAccessor.php: -------------------------------------------------------------------------------- 1 | case)?->metadata; 28 | 29 | if ($metadata === null) { 30 | return null; 31 | } 32 | 33 | // Handle JSON string metadata 34 | if (is_string($metadata) && str_starts_with(trim($metadata), '{')) { 35 | $decoded = json_decode($metadata, true); 36 | if (json_last_error() === JSON_ERROR_NONE) { 37 | $metadata = $decoded; 38 | } 39 | } 40 | 41 | if (! is_array($metadata)) { 42 | return null; 43 | } 44 | 45 | return $metadata[$name] ?? null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Traits/BackedEnum.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | public static function asSelectArray(): array 19 | { 20 | $enumClass = static::class; 21 | 22 | return array_map(function ($case) use ($enumClass): array { 23 | // If the enum class has a label and id method, use them to get the text and value. 24 | if (method_exists($enumClass, 'label') && method_exists($enumClass, 'id')) { 25 | return [ 26 | 'text' => self::label($case->name) ?? self::toLabel($case->value), 27 | 'value' => self::id($case->name) ?? $case->value, 28 | ]; 29 | } 30 | 31 | return [ 32 | 'text' => self::toLabel($case->value), 33 | 'value' => $case->value, 34 | ]; 35 | }, $enumClass::cases()); 36 | } 37 | 38 | /** 39 | * Maps a scalar to an enum instance. 40 | * 41 | * @param string $name The case name. 42 | * @return mixed 43 | */ 44 | public static function fromName(string $name): mixed 45 | { 46 | return self::__fromName($name); 47 | } 48 | 49 | /** 50 | * Retrieve the case name for the given simpler value. 51 | * 52 | * @param int|string $value The simpler value. 53 | * @return string|null The case name. 54 | */ 55 | public static function name(int|string $value): ?string 56 | { 57 | $enum = self::tryFrom($value); 58 | 59 | return $enum ? $enum->name : null; 60 | } 61 | 62 | /** 63 | * Retrieve an array containing all of the case names. 64 | * 65 | * @return array An array of case names. 66 | */ 67 | public static function names(): array 68 | { 69 | return array_column(self::cases(), 'name'); 70 | } 71 | 72 | /** 73 | * Create a label from the case name. 74 | * 75 | * @param int|string $value The simpler value. 76 | * @param bool $isConst Case format is a traditional "CONSTANT_CASE" string. 77 | * @return string The created label. 78 | */ 79 | public static function toLabel(int|string $value, bool $isConst = false): string 80 | { 81 | $enum = self::tryFrom($value); 82 | 83 | $name = $enum ? $enum->name : ''; 84 | $name = $isConst ? Str::splitConstantCase($name) : Str::splitEnumCase($name); 85 | 86 | return $name ? Str::title($name) : ''; 87 | } 88 | 89 | /** 90 | * Create and compile an array of labels from the case names. 91 | * 92 | * @param bool $isConst Case format is a traditional "CONSTANT_CASE" string. 93 | * @return array The created labels. 94 | */ 95 | public static function toLabels(bool $isConst = false): array 96 | { 97 | return array_map(function ($name) use ($isConst): string { 98 | return Str::title($isConst ? Str::splitConstantCase($name) : Str::splitEnumCase($name)); 99 | }, array_column(self::cases(), 'name')); 100 | } 101 | 102 | /** 103 | * Maps a scalar to an enum instance or null. 104 | * 105 | * @param string $name The case name. 106 | * @return mixed 107 | */ 108 | public static function tryFromName(string $name): mixed 109 | { 110 | return self::__tryFromName($name); 111 | } 112 | 113 | /** 114 | * Retrieve the simpler value for the given case name. 115 | * 116 | * @param string $name The case name. 117 | * @return int|string|null The simpler value. 118 | */ 119 | public static function value(string $name): int|string|null 120 | { 121 | $enumClass = static::class; 122 | 123 | foreach ($enumClass::cases() as $case) { 124 | if ($case->name === $name) { 125 | return $case->value; 126 | } 127 | } 128 | 129 | return null; 130 | } 131 | 132 | /** 133 | * Retrieve an array containing all of the simpler values. 134 | * 135 | * @return array An array of simpler values. 136 | */ 137 | public static function values(): array 138 | { 139 | return array_column(self::cases(), 'value'); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Traits/ExtendsBackedEnum.php: -------------------------------------------------------------------------------- 1 | name === $name; 36 | }); 37 | 38 | // Unwrap the enum object. 39 | $enum = array_shift($enum); 40 | 41 | return $enum ? $enum : null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Traits/HasAttributes.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public static function attributes(?string $name = null, ?array $filter = null): array 24 | { 25 | $attributes = []; 26 | foreach (static::cases() as $case) { 27 | if ($name && $case->name !== $name) { 28 | continue; 29 | } 30 | $attributes[$case->name] = self::getAttributes($case, $filter); 31 | } 32 | 33 | if ($name && count($attributes) === 1) { 34 | return $attributes[$name]; 35 | } 36 | 37 | return $attributes; 38 | } 39 | 40 | /** 41 | * Retrieve the description attribute. 42 | * 43 | * @param string $name The case name. 44 | * @return string|null 45 | */ 46 | public static function description(string $name): ?string 47 | { 48 | return self::mapAttributeClass(Description::class, self::mapFromCase($name))?->description; 49 | } 50 | 51 | /** 52 | * Retrieve the description attribute for all cases. 53 | * 54 | * @param 'name'|'value'|null $keyBy Whether to key the result by case name or case value. Defaults to zero-indexed array. 55 | * @return array 56 | */ 57 | public static function descriptions(?string $keyBy = null): array 58 | { 59 | return self::mapCasesTo(fn ($name) => self::description($name), $keyBy); 60 | } 61 | 62 | /** 63 | * Retrieve the id attribute. 64 | * 65 | * @param string $name The case name. 66 | * @return int|string|null 67 | */ 68 | public static function id(string $name): int|string|null 69 | { 70 | $enum = self::mapFromCase($name); 71 | 72 | return self::mapAttributeClass(Id::class, $enum)?->id; 73 | } 74 | 75 | /** 76 | * Retrieve the id attribute for all cases. 77 | * 78 | * @param 'name'|'value'|null $keyBy Whether to key the result by case name or case value. Defaults to zero-indexed array. 79 | * @return array 80 | */ 81 | public static function ids(?string $keyBy = null): array 82 | { 83 | return self::mapCasesTo(fn ($name) => self::id($name), $keyBy); 84 | } 85 | 86 | /** 87 | * Retrieve the label attribute. 88 | * 89 | * @param string $name The case name. 90 | * @return string|null 91 | */ 92 | public static function label(string $name): ?string 93 | { 94 | $enum = self::mapFromCase($name); 95 | 96 | return self::mapAttributeClass(Label::class, $enum)?->label; 97 | } 98 | 99 | /** 100 | * Retrieve the label attribute for all cases. 101 | * 102 | * @param 'name'|'value'|null $keyBy Whether to key the result by case name or case value. Defaults to zero-indexed array. 103 | * @return array 104 | */ 105 | public static function labels(?string $keyBy = null): array 106 | { 107 | return self::mapCasesTo(fn ($name) => self::label($name), $keyBy); 108 | } 109 | 110 | /** 111 | * Retrieve the metadata attribute or specific metadata values by key(s). 112 | * 113 | * @param string $name The case name. 114 | * @param string|array|null $key Optional key or array of keys to retrieve specific metadata values. 115 | * @return mixed 116 | */ 117 | public static function metadata(string $name, string|array|null $key = null): mixed 118 | { 119 | $metadata = self::mapAttributeClass(Metadata::class, self::mapFromCase($name))?->metadata; 120 | 121 | // Handle JSON string metadata 122 | if (is_string($metadata) && str_starts_with(trim($metadata), '{')) { 123 | $decoded = json_decode($metadata, true); 124 | if (json_last_error() === JSON_ERROR_NONE) { 125 | $metadata = $decoded; 126 | } 127 | } 128 | 129 | if ($key === null || ! is_array($metadata)) { 130 | return $metadata; 131 | } 132 | 133 | if (is_array($key)) { 134 | return array_intersect_key($metadata, array_flip($key)); 135 | } 136 | 137 | return $metadata[$key] ?? null; 138 | } 139 | 140 | /** 141 | * Retrieve the metadata attribute for all cases, optionally filtered by metadata key(s). 142 | * 143 | * @param string|array|null $key Optional key or array of keys to retrieve specific metadata values. 144 | * @param 'name'|'value'|null $keyBy Whether to key the result by case name or case value. Defaults to zero-indexed array. 145 | * @return array 146 | */ 147 | public static function metadatum(string|array|null $key = null, ?string $keyBy = null): array 148 | { 149 | return self::mapCasesTo(fn ($name) => self::metadata($name, $key), $keyBy); 150 | } 151 | 152 | /** 153 | * Get a metadata accessor for property-like access to metadata values. 154 | * 155 | * @return MetadataAccessor 156 | */ 157 | public function meta(): MetadataAccessor 158 | { 159 | return new MetadataAccessor($this); 160 | } 161 | 162 | /** 163 | * Retrieve all of the attributes. 164 | * 165 | * @param self $enum The enum instance. 166 | * @param array|null $filter Filter by attributes array. 167 | * @return array 168 | */ 169 | protected static function getAttributes(self $enum, ?array $filter = null): array 170 | { 171 | return $filter ? array_filter([ 172 | 'simpleValue' => $enum->value, 173 | 'description' => self::description($enum->name), 174 | 'id' => self::id($enum->name), 175 | 'label' => self::label($enum->name), 176 | 'metadata' => self::metadata($enum->name), 177 | ], function ($key) use ($filter) { 178 | return in_array($key, $filter); 179 | }, ARRAY_FILTER_USE_KEY) : [ 180 | 'simpleValue' => $enum->value, 181 | 'description' => self::description($enum->name), 182 | 'id' => self::id($enum->name), 183 | 'label' => self::label($enum->name), 184 | 'metadata' => self::metadata($enum->name), 185 | ]; 186 | } 187 | 188 | /** 189 | * Maps an attribute class to an enum instance. 190 | * 191 | * @param string $attribute The attribute class. 192 | * @param self $enum The enum instance. 193 | * @return mixed 194 | */ 195 | protected static function mapAttributeClass(string $attribute, self $enum): mixed 196 | { 197 | $ref = new ReflectionClassConstant(self::class, $enum->name); 198 | $classAttributes = $ref->getAttributes($attribute); 199 | 200 | if (count($classAttributes) === 0) { 201 | return null; 202 | } 203 | 204 | return $classAttributes[0]->newInstance(); 205 | } 206 | 207 | /** 208 | * Maps a scalar to an enum instance. 209 | * 210 | * @param string $name The case name. 211 | * @return mixed 212 | */ 213 | protected static function mapFromCase(string $name): mixed 214 | { 215 | return self::__tryFromName($name); 216 | } 217 | 218 | /** 219 | * Map enum cases to a given callback, optionally keyed by 'name' or 'value'. 220 | * 221 | * @template T 222 | * 223 | * @param callable(string): T $callback A function that receives the enum case name as string. 224 | * @param 'name'|'value'|null $keyBy 225 | * @return array 226 | */ 227 | protected static function mapCasesTo(callable $callback, ?string $keyBy = null): array 228 | { 229 | $cases = static::cases(); // use static for late binding 230 | 231 | if ($keyBy === null) { 232 | return array_map( 233 | fn ($case) => $callback($case->name), 234 | $cases 235 | ); 236 | } 237 | 238 | $keys = match ($keyBy) { 239 | 'value' => array_column($cases, 'value'), 240 | default => array_column($cases, 'name'), 241 | }; 242 | 243 | return array_map( 244 | fn ($case) => $callback($case->name), 245 | array_combine($keys, $cases) 246 | ); 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /tests/Arch.php: -------------------------------------------------------------------------------- 1 | expect('Iteks\Support\Facades\Enum') 6 | ->toOnlyUse([ 7 | 'Illuminate\Support\Facades\Facade', 8 | 'Iteks\Support\Services\EnumService', 9 | ]); 10 | 11 | test('service providers') 12 | ->expect('Iteks\Support\EnumServiceProvider') 13 | ->toOnlyUse([ 14 | 'Illuminate\Support\ServiceProvider', 15 | 'Illuminate\Support\Str', 16 | 'Iteks\Support\Facades\Enum', 17 | 'Iteks\Support\Macros\Str', 18 | 'Iteks\Support\Services\BackedEnumService', 19 | 'Iteks\Support\Services\EnumService', 20 | 'Iteks\Support\Services\HasAttributesService', 21 | ]); 22 | })->group('arch'); 23 | -------------------------------------------------------------------------------- /tests/Facades/Enum.php: -------------------------------------------------------------------------------- 1 | register(); 12 | 13 | Enum::setFacadeApplication($app); 14 | 15 | $array = Enum::asSelectArray(BackedEnumShape::class); 16 | 17 | expect($array)->toBeArray(); 18 | }); 19 | })->group('facade'); 20 | -------------------------------------------------------------------------------- /tests/Pest.php: -------------------------------------------------------------------------------- 1 | beforeEach(function () { 4 | // Create a fresh copy of the application for each test. 5 | $app = \Illuminate\Foundation\Application::getInstance(); 6 | 7 | // // Manually register the package's service provider. 8 | $app->register(\Iteks\Support\EnumServiceProvider::class); 9 | 10 | // Boot the application. 11 | $app->boot(); 12 | })->in('Facades'); 13 | -------------------------------------------------------------------------------- /tests/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | register(); 15 | 16 | expect($app->get(Enum::class))->toBeInstanceOf(Enum::class); 17 | }); 18 | 19 | it('registers the enum service on the container as singleton', function () { 20 | $app = app(); 21 | 22 | (new EnumServiceProvider($app))->register(); 23 | 24 | $enum = $app->get(Enum::class); 25 | 26 | expect($enum)->toBeInstanceOf(Enum::class); 27 | }); 28 | 29 | it('boots str macros on the container', function () { 30 | $app = app(); 31 | 32 | (new EnumServiceProvider($app))->register(); 33 | 34 | Enum::setFacadeApplication($app); 35 | 36 | $array = Enum::asSelectArray(BackedEnumShape::class); 37 | 38 | expect($array)->toBeArray(); 39 | }); 40 | 41 | it('provides', function () { 42 | $app = app(); 43 | 44 | $provides = (new EnumServiceProvider($app))->provides(); 45 | 46 | expect($provides)->toBe([ 47 | 'enum', 48 | EnumService::class, 49 | BackedEnumService::class, 50 | HasAttributesService::class, 51 | ]); 52 | }); 53 | })->group('service-provider'); 54 | -------------------------------------------------------------------------------- /tests/Unit/Attributes.php: -------------------------------------------------------------------------------- 1 | toBe('A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center'); 11 | }); 12 | 13 | it('retrieves all descriptions', function () { 14 | $descriptions = Enum::descriptions(BackedEnumShape::class); 15 | 16 | expect($descriptions)->toHaveCount(5) 17 | ->and($descriptions)->toContain('A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center') 18 | ->and($descriptions)->toContain('A square is a regular quadrilateral, all sides have equal length and all angles are 90 degrees') 19 | ->and($descriptions)->toContain('A triangle is a polygon with three edges and three vertices') 20 | ->and($descriptions)->toContain('A star is a self-intersecting, equilateral polygon') 21 | ->and($descriptions)->toContain('A rectangle is a quadrilateral with four right angles'); 22 | }); 23 | })->group('attributes'); 24 | -------------------------------------------------------------------------------- /tests/Unit/Attributes/AttributeCapabilitiesTest.php: -------------------------------------------------------------------------------- 1 | getAttributes(Description::class); 16 | 17 | expect($attributes)->toHaveCount(1) 18 | ->and($attributes[0]->newInstance()->description)->toBe('A collection of geometric shapes, used for demonstrating enum attributes'); 19 | }); 20 | 21 | it('supports case-level and multiple descriptions', function () { 22 | $reflector = new ReflectionClass(BackedEnumShape::class); 23 | $constant = $reflector->getReflectionConstant('RoundCircle'); 24 | $attributes = $constant->getAttributes(Description::class); 25 | 26 | expect($attributes)->toHaveCount(1) 27 | ->and($attributes[0]->newInstance()->description)->toBe('A circle is a perfectly round geometric figure, every point on the circle is equidistant from the center'); 28 | }); 29 | 30 | it('supports only case-level id', function () { 31 | $reflector = new ReflectionClass(BackedEnumShape::class); 32 | 33 | // Class should not have Id attribute. 34 | expect($reflector->getAttributes(Id::class))->toHaveCount(0); 35 | 36 | // Case should have Id attribute. 37 | $constant = $reflector->getReflectionConstant('RoundCircle'); 38 | $attributes = $constant->getAttributes(Id::class); 39 | expect($attributes)->toHaveCount(1) 40 | ->and($attributes[0]->newInstance()->id)->toBe(1); 41 | }); 42 | 43 | it('supports only case-level label', function () { 44 | $reflector = new ReflectionClass(BackedEnumShape::class); 45 | 46 | // Class should not have Label attribute. 47 | expect($reflector->getAttributes(Label::class))->toHaveCount(0); 48 | 49 | // Case should have Label attribute. 50 | $constant = $reflector->getReflectionConstant('RoundCircle'); 51 | $attributes = $constant->getAttributes(Label::class); 52 | expect($attributes)->toHaveCount(1) 53 | ->and($attributes[0]->newInstance()->label)->toBe('Round Circle'); 54 | }); 55 | 56 | it('supports class-level and multiple metadata', function () { 57 | $reflector = new ReflectionClass(BackedEnumShape::class); 58 | $attributes = $reflector->getAttributes(Metadata::class); 59 | 60 | expect($attributes)->toHaveCount(1) 61 | ->and($attributes[0]->newInstance()->metadata)->toBe(['version' => '1.0', 'category' => 'geometry']); 62 | }); 63 | 64 | it('supports case-level and multiple metadata', function () { 65 | $reflector = new ReflectionClass(BackedEnumShape::class); 66 | $constant = $reflector->getReflectionConstant('BoxSquare'); 67 | $attributes = $constant->getAttributes(Metadata::class); 68 | 69 | $expectedMetadata = json_decode('{"color": "blue", "sides": 4, "type": "polygon", "regular": true}', true); 70 | $actualMetadata = json_decode($attributes[0]->newInstance()->metadata, true); 71 | 72 | expect($attributes)->toHaveCount(1) 73 | ->and($actualMetadata)->toBe($expectedMetadata); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /tests/Unit/Attributes/MetadataPropertyAccessTest.php: -------------------------------------------------------------------------------- 1 | meta()->color)->toBe('red') 10 | ->and(BackedEnumShape::RoundCircle->meta()->sides)->toBe(0) 11 | ->and(BackedEnumShape::RoundCircle->meta()->type)->toBe('curved') 12 | ->and(BackedEnumShape::BoxSquare->meta()->color)->toBe('blue') 13 | ->and(BackedEnumShape::BoxSquare->meta()->sides)->toBe(4) 14 | ->and(BackedEnumShape::BoxSquare->meta()->regular)->toBeTrue(); 15 | }); 16 | 17 | it('can access JSON string metadata as properties', function () { 18 | expect(BackedEnumShape::BoxSquare->meta()->color)->toBe('blue') 19 | ->and(BackedEnumShape::BoxSquare->meta()->sides)->toBe(4) 20 | ->and(BackedEnumShape::BoxSquare->meta()->type)->toBe('polygon') 21 | ->and(BackedEnumShape::BoxSquare->meta()->regular)->toBeTrue() 22 | ->and(BackedEnumShape::PointedStar->meta()->color)->toBe('yellow') 23 | ->and(BackedEnumShape::PointedStar->meta()->points)->toBe(5) 24 | ->and(BackedEnumShape::PointedStar->meta()->symmetrical)->toBeTrue(); 25 | }); 26 | 27 | it('accessing non-existent metadata property returns null', function () { 28 | expect(BackedEnumShape::RoundCircle->meta()->nonexistent)->toBeNull() 29 | ->and(BackedEnumShape::BoxSquare->meta()->nonexistent)->toBeNull() 30 | ->and(BackedEnumShape::PointedStar->meta()->sides)->toBeNull() 31 | ->and(BackedEnumShape::RightTriangle->meta()->regular)->toBeNull(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/Unit/Methods.php: -------------------------------------------------------------------------------- 1 | toBeArray() 10 | ->each->toHaveKeys(['text', 'value']); 11 | }); 12 | })->group('methods'); 13 | --------------------------------------------------------------------------------