Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to this project will be documented in this file.

## 8.5.1 - Unreleased

### Changed

- Documentation puts the `is<Case>()` checks first, with a case-name-to-check table (`IN_PROGRESS` → `isInProgress()`), the names that are not checks and what to write instead, and a status-branching example.

## 8.5.0 - 2026-10-09

### Added
Expand Down
6 changes: 3 additions & 3 deletions docs/landing/01-hero.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ badges:
- label: "{package.downloadsShort}+ downloads"
color: blue
eyebrow: "{package.name}"
title: Native enums,
highlight: a little smarter
title: Native enums that
highlight: know isActive()
buttons:
- label: View Full Documentation
href: "{card.docsUrl}"
Expand All @@ -27,4 +27,4 @@ labels:
copied: Copied!
---

Add `use SmartEnum;` to any native PHP enum and get helpers for names, values, labels, options, lookup by case name, strict comparisons and `isActive()`-style checks. Your enums stay native: no base class, no code generation, no framework.
Add `use SmartEnum;` to any native PHP enum and every case can answer for itself: `$status->isActive()` for `Active`, `$order->isInProgress()` for `IN_PROGRESS`. You also get helpers for names, values, labels, options, lookup by case name and strict comparisons. Your enums stay native: no base class, no code generation, no framework.
6 changes: 3 additions & 3 deletions docs/landing/02-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ badge: Key Features
title: Everything your enums are missing
subtitle: One trait, added to the enums you already have.
items:
- icon: badge-check
title: isActive() for Every Case
text: "Each case gets its own check, named from the case: `isActive()` for `Active`, `isInProgress()` for `IN_PROGRESS`, `isOnHold()` for `onHold`."
- icon: lines
title: Names, Values & Labels
text: "`names()`, `values()` and `labels()` list your cases in the shape you need."
Expand All @@ -17,9 +20,6 @@ items:
- icon: check-circle
title: Strict Comparisons
text: "`eq()`, `is()`, `isNot()`, `in()` and `notIn()` match a case, a case name or a backing value, with no type juggling."
- icon: badge-check
title: Case Checks
text: "Every case gets its own check: `$status->isActive()`, `$order->isInCooking()`."
- icon: switch
title: Unit & Backed Enums
text: The same trait works on unit, string-backed and int-backed enums, and custom labels flow into `labels()` and `options()`.
Expand Down
21 changes: 12 additions & 9 deletions docs/landing/03-quick-look.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
---
view: components.packages.quick-look
title: A quick look
subtitle: Add the trait to a native enum and use its helpers next to cases(), from() and tryFrom().
file: app/Enums/Status.php
subtitle: Add the trait and every case answers its own is…() check, whatever style its name is written in.
file: app/Enums/OrderStatus.php
language: php
code: |
use Pharaonic\SmartEnum\SmartEnum;

enum Status: string
enum OrderStatus: string
{
use SmartEnum;

case Pending = 'pending';
case Active = 'active';
case Disabled = 'disabled';
case PENDING = 'pending';
case IN_PROGRESS = 'in_progress';
case OUT_FOR_DELIVERY = 'out_for_delivery';
case Delivered = 'delivered';
}

Status::options(); // ['pending' => 'Pending', 'active' => 'Active', ...]
Status::fromName('Active')->isActive(); // true
Status::Active->in(['pending', 'active']); // true
$status = OrderStatus::IN_PROGRESS;

$status->isInProgress(); // true
$status->isOutForDelivery(); // false
$status->in(['pending', 'in_progress']); // true
---
4 changes: 4 additions & 0 deletions docs/landing/04-faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Yes. A unit enum has no backing values, so `values()`, `options()`, `hasValue()`

Declare a `label()` method in the enum, or define a global `smart_enum_label()` function for every enum at once. `labels()`, `options()` and `info()` pick the change up.

## How does IN_PROGRESS become isInProgress()?

The check is `is` plus the case name, with each underscore-separated word capitalized. A word written all in upper case is title-cased first, so `IN_PROGRESS` becomes `isInProgress()`, `PENDING` becomes `isPending()` and `onHold` becomes `isOnHold()`.

## Will my IDE know about $status->isActive()?

The `is<Case>()` checks are resolved at runtime, so add `@method bool isActive()` tags to the enum's docblock for IDEs and PHPStan.
Expand Down
10 changes: 5 additions & 5 deletions docs/sections/01-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@

# Smart Enum

Smart, lightweight helpers for native PHP enums. Add the `SmartEnum` trait to any enum you already have and get helpers for case names, backing values, labels, select options, lookup by case name, comparisons and `is<Case>()` checks. Native methods (`cases()`, `from()`, `tryFrom()`) stay native, and the package has no base class, no generated code and no runtime dependencies.
Smart, lightweight helpers for native PHP enums. Add the `SmartEnum` trait to any enum you already have and every case can answer for itself: `$status->isActive()` for `Active`, `$order->isInProgress()` for `IN_PROGRESS`. You also get helpers for case names, backing values, labels, select options, lookup by case name and comparisons. Native methods (`cases()`, `from()`, `tryFrom()`) stay native, and the package has no base class, no generated code and no runtime dependencies.

:::features
### isActive() for Every Case {icon="badge-check"}
Each case gets its own check, named from the case: IN_PROGRESS answers isInProgress().

### Names, Values & Labels {icon="lines"}
List the case names, the backing values, or the labels of every case.

Expand All @@ -21,13 +24,10 @@ Find a case by its name, the same way from() finds one by its value.
### Comparisons {icon="check-circle"}
Compare a case with another case, a case name or a backing value, always strictly.

### Case Checks {icon="badge-check"}
Ask a case about itself with isActive(), isPending() and one check per case.

### Unit & Backed Enums {icon="switch"}
The same trait works on unit, string-backed and int-backed enums.
:::

:::info Quick Tip
Keep using PHP's own `Status::cases()`, `Status::from()` and `Status::tryFrom()`. SmartEnum only adds what PHP doesn't have.
Write case names in the style you like. `IN_PROGRESS`, `InProgress` and `In_Progress` all get the same check, `isInProgress()`, and PHP's own `cases()`, `from()` and `tryFrom()` keep working as before.
:::
25 changes: 25 additions & 0 deletions docs/sections/03-basic-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,29 @@ $status->in([Status::Active, Status::Pending]); // true
$status->isActive(); // true
```

### Ask a case what it is

Every case answers an `is<Case>()` check named after it. The name is built from the case name, so it reads naturally whatever style your cases use:

```php title="app/Enums/OrderStatus.php"
enum OrderStatus: string
{
use SmartEnum;

case PENDING = 'pending';
case IN_PROGRESS = 'in_progress';
case OUT_FOR_DELIVERY = 'out_for_delivery';
case Delivered = 'delivered';
}
```

```php
$status = OrderStatus::IN_PROGRESS;

$status->isPending(); // false
$status->isInProgress(); // true
$status->isOutForDelivery(); // false
$status->isDelivered(); // false
```

See [Static Methods](#static-methods), [Instance Methods](#instance-methods) and [Case Checks](#case-checks) for each one.
50 changes: 48 additions & 2 deletions docs/sections/06-case-checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,29 @@ $status->isPending(); // false
$status->isDisabled(); // false
```

Any arguments you pass to a check are ignored.
You don't declare the checks: adding a case adds its check.

### From case name to check

| Case | Check |
| --- | --- |
| `Active` | `isActive()` |
| `PENDING` | `isPending()` |
| `IN_COOKING` | `isInCooking()` |
| `IN_PROGRESS` | `isInProgress()` |
| `OUT_FOR_DELIVERY` | `isOutForDelivery()` |
| `InProgress` | `isInProgress()` |
| `In_Progress` | `isInProgress()` |
| `onHold` | `isOnHold()` |
| `HTTPError` | `isHTTPError()` |
| `Level2` | `isLevel2()` |

```php
$status = OrderStatus::IN_PROGRESS;

$status->isInProgress(); // true
$status->isOutForDelivery(); // false
```

### How check names are built

Expand All @@ -19,7 +41,31 @@ The check name is `is` followed by the case name, with each underscore-separated
- A word written entirely in upper case is title-cased: `PENDING` becomes `isPending`, `IN_COOKING` becomes `isInCooking`.
- Any other word keeps its letters and gets an upper-case first letter: `onHold` becomes `isOnHold`, `HTTPError` becomes `isHTTPError`, `Level2` becomes `isLevel2`.

Check names are matched exactly. `isPENDING()`, `isonHold()`, `isHttpError()` and `IsPending()` are not checks, and calling them throws `Error: Call to undefined method`.
### Names that are not checks

Check names are matched exactly, letter case included. Each call below throws `Error: Call to undefined method`:

| Call | Why it fails | Write instead |
| --- | --- | --- |
| `isArchived()` | No case is named `Archived` | Add the case, or use `is('Archived')` for input you don't control |
| `pending()` | Checks start with `is` | `isPending()` |
| `hasPending()` | Only the `is` prefix makes a check | `isPending()` |
| `IsPending()` | The prefix is lower-case `is` | `isPending()` |
| `isPENDING()` | The raw case name is not used | `isPending()` |
| `isIN_COOKING()` | Underscores are removed | `isInCooking()` |
| `isinprogress()` | Each word starts with a capital letter | `isInProgress()` |
| `isonHold()` | The first letter after `is` is a capital | `isOnHold()` |
| `isHttpError()` | Mixed-case words keep their letters | `isHTTPError()` |
| `secret()` | A `protected` or `private` enum method can't be called from outside | Make it `public` |

```php
OrderStatus::PENDING->isPENDING();
// Error: Call to undefined method App\Enums\OrderStatus::isPENDING()
```

### Arguments are ignored

A check takes no arguments. Anything you pass is ignored, so `$status->isActive('anything', 1)` is the same as `$status->isActive()`.

### Ambiguous checks

Expand Down
27 changes: 22 additions & 5 deletions docs/sections/10-examples.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
## Examples

### 1. A select input
### 1. Branching on a status

Read the checks like plain English when the code depends on the current case.

```php title="src/Orders/OrderNotifier.php"
public function notify(Order $order): void
{
if ($order->status->isOutForDelivery()) {
$this->sms->send($order->phone, 'Your order is on its way.');
}

if ($order->status->isDelivered()) {
$this->mailer->send(new OrderDelivered($order));
}
}
```

### 2. A select input

Build `<option>` tags from `options()`, which maps each backing value to its label.

Expand Down Expand Up @@ -33,7 +50,7 @@ Build `<option>` tags from `options()`, which maps each backing value to its lab
</select>
```

### 2. Validating input
### 3. Validating input

Check a submitted value before converting it with PHP's own `from()`.

Expand All @@ -45,7 +62,7 @@ if (! Status::hasValue($input['status'])) {
$status = Status::from($input['status']);
```

### 3. Guarding a state change
### 4. Guarding a state change

Allow an action only for some cases.

Expand All @@ -60,15 +77,15 @@ public function suspend(): void
}
```

### 4. Configuration by case name
### 5. Configuration by case name

Read a case name from configuration or the environment and resolve it, with a fallback.

```php title="config/app.php"
$defaultStatus = Status::tryFromName(getenv('DEFAULT_STATUS') ?: '') ?? Status::Pending;
```

### 5. Serializing for an API
### 6. Serializing for an API

Return the case metadata from `info()` in a JSON response.

Expand Down
Loading