Andy Hinkle

Stop Passing Booleans As Arguments

If you've ever seen a method call like this:

1$order->cancel(silent: true);

You might pause and ask: what does silent actually mean here? Now imagine the caller doesn't use named arguments:

1$order->cancel(true);

That true tells you nothing. You have to dig into the method signature to understand what's happening. And when a second boolean flag inevitably gets added, things get worse:

1$order->cancel(true, false);

Before long, someone adds a reason and a notification override, and now you're staring at this:

1$order->cancel(true, false, 'customer_request', true, null);

Good luck figuring out what any of that means without reading the method signature.

Boolean flags hide intent. They're a code smell that signals your method is doing two different things based on a toggle, and that toggle is invisible at the call site.

The Before

Here's a controller that cancels an order. It accepts a bool $silent flag to optionally skip sending a notification to the customer:

1class OrderController extends Controller
2{
3 public function cancel(Order $order, bool $silent = false)
4 {
5 $order->markAsCancelled(silent: $silent);
6 
7 return redirect()->route('orders.index')
8 ->with('success', 'Order cancelled.');
9 }
10}

And the event listener that handles post-cancellation logic:

1class SendOrderCancellationNotification
2{
3 public function handle(OrderCancelled $event): void
4 {
5 if (! $event->silent) {
6 $event->order->customer->notify(
7 new OrderCancelledNotification($event->order)
8 );
9 }
10 }
11}

This works. But $event->silent is a boolean doing heavy lifting. It controls whether the customer gets notified. What happens when you need a third mode, say you want to cancel and issue a refund? Do you add another boolean? $order->markAsCancelled(silent: false, refund: true)?

That's a path you don't want to go down.

The Enum

Instead, replace the boolean with an enum that names the behavior:

1enum CancellationMode: string
2{
3 case Default = 'default';
4 case Silent = 'silent';
5}

Now the intent is explicit. CancellationMode::Silent says exactly what it does. And when you need that third mode six months from now, you add a case instead of a boolean.

The After

The controller now accepts a string and resolves it to the enum:

1class OrderController extends Controller
2{
3 public function cancel(Order $order, string $mode = 'default')
4 {
5 $mode = CancellationMode::tryFrom($mode) ?? CancellationMode::Default;
6 
7 $order->markAsCancelled(mode: $mode);
8 
9 return redirect()->route('orders.index')
10 ->with('success', 'Order cancelled.');
11 }
12}

The event now carries a typed mode instead of a bare boolean:

1class OrderCancelled
2{
3 use Dispatchable;
4 use SerializesModels;
5 
6 public function __construct(
7 public Order $order,
8 public CancellationMode $mode = CancellationMode::Default,
9 ) {}
10}

And the listener reads like plain English:

1class SendOrderCancellationNotification
2{
3 public function handle(OrderCancelled $event): void
4 {
5 if ($event->mode !== CancellationMode::Silent) {
6 $event->order->customer->notify(
7 new OrderCancelledNotification($event->order)
8 );
9 }
10 }
11}

$event->mode !== CancellationMode::Silent is immediately understandable. No need to parse what ! $event->silent means in context.

Why Not Always an Enum?

Enums aren't always the answer. If the behavior you're toggling is more like a set of independent options rather than a single mode, a fluent builder can be a better fit:

1$order->cancel()
2 ->notifyVia('email')
3 ->withRefund()
4 ->reason('Customer requested');

Each method call is self-documenting and the options are independent of each other. You'd never try to cram all of that into a single enum. Enums work best when you're picking one mode from a set of mutually exclusive behaviors. Fluent builders work best when you're composing multiple independent options together.

Why This Matters

Most of the time though, an enum is exactly what I reach for.

The call site actually reads. markAsCancelled(mode: CancellationMode::Silent) tells you everything. markAsCancelled(silent: true) only makes sense if you already know the parameter name, and without named arguments you're just guessing.

New modes are trivial. Need a NotifyViaEmail case down the road? Add one line to the enum.

You can actually search for them. CancellationMode::Silent across your codebase finds every silent cancellation.

Next time you reach for a bool $flag parameter, consider whether it should be an enum or a fluent method instead. Either way, stop passing booleans.

Cheers