Boolean parameters (flag arguments) make code unclear:
- Unclear intent - What does
truemean? - Hard to read -
create_user('john', 'email@test.com', true, false, true)- what?
- Function does too much - Boolean usually means "do this OR that"
- Easy to mix up - Which boolean was which?
- Hard to extend - Adding more booleans makes it worse
function get_posts($published_only = true) {
if ($published_only) {
return get_posts(['post_status' => 'publish']);
} else {
return get_posts(['post_status' => 'any']);
}
}
// Usage - what does true mean?
$posts = get_posts(true);
$all = get_posts(false);Problems:
- Reader must look at function definition to understand what
truedoes - At call site,
get_posts(true)tells you nothing - Not self-documenting
create_user('john', 'john@example.com', true, false, true);What does this mean?
- First true: Send welcome email? Make admin? Auto-login?
- False: Don't send email? Not admin? No auto-login?
- Second true: ???
You have to read the function signature to understand!
function get_published_posts(): array {
return get_posts( [ 'post_status' => 'publish' ] );
}
function get_all_posts(): array {
return get_posts( [ 'post_status' => 'any' ] );
}
// Usage - crystal clear!
$posts = get_published_posts();
$all = get_all_posts();Benefits:
- Immediately clear what's happening
- Self-documenting code
- Can't call it wrong
enum PostFilter: string {
case PUBLISHED_ONLY = 'publish';
case DRAFTS_ONLY = 'draft';
case ALL = 'any';
}
function get_posts( PostFilter $filter = PostFilter::PUBLISHED_ONLY ): array {
return get_posts( [ 'post_status' => $filter->value ] );
}
// Usage - type-safe and clear
$posts = get_posts( PostFilter::PUBLISHED_ONLY );
$drafts = get_posts( PostFilter::DRAFTS_ONLY );
$all = get_posts( PostFilter::ALL );Benefits:
- IDE autocomplete shows available options
- Can't pass invalid values
- Self-documenting
- Easy to extend (add more cases)
For complex object creation with many options:
$user_id = ( new UserCreator( 'john', 'john@example.com' ) )
->with_welcome_email()
->with_auto_login()
->create();
$admin_id = ( new UserCreator( 'admin', 'admin@example.com' ) )
->as_admin()
->create();Benefits:
- Fluent, readable API
- Only specify what you need
- Can't mix up parameter order
- Easy to extend with new options
When you must have optional parameters:
function create_post(
string $title,
string $content,
string $status = 'draft',
bool $enable_comments = true,
bool $sticky = false
): int {
// ...
}
// Usage - named arguments make intent clear
$post_id = create_post(
title: 'My Post',
content: 'Content here',
status: 'publish',
enable_comments: false,
sticky: true
);Benefits:
- Clear what each argument means
- Can skip optional parameters
- Can't mix up order
class UserNameDisplayOptions {
public function __construct(
public readonly bool $include_email = false,
public readonly bool $uppercase = false,
public readonly bool $link_to_profile = false
) {}
}
function display_user_name(
int $user_id,
UserNameDisplayOptions $options = new UserNameDisplayOptions()
): string {
// ...
}
// Usage
echo display_user_name(1, new UserNameDisplayOptions(
include_email: true,
link_to_profile: true
));// BAD: What's the 4th and 5th parameter?
wp_mail('to@example.com', 'Subject', 'Message', '', '', true);
// The signature is:
// wp_mail($to, $subject, $message, $headers = '', $attachments = [], $more_params)
// That last true? No idea what it does without looking it up!class EmailSender {
private string $to;
private string $subject;
private string $message;
private array $headers = [];
private array $attachments = [];
public function __construct(string $to, string $subject, string $message) {
$this->to = $to;
$this->subject = $subject;
$this->message = $message;
}
public function with_headers(array $headers): self {
$this->headers = $headers;
return $this;
}
public function with_attachments(array $attachments): self {
$this->attachments = $attachments;
return $this;
}
public function send(): bool {
return wp_mail(
$this->to,
$this->subject,
$this->message,
$this->headers,
$this->attachments
);
}
}
// Usage - clear and fluent
$sent = (new EmailSender('to@example.com', 'Subject', 'Message'))
->with_headers(['From: noreply@example.com'])
->with_attachments(['/path/to/file.pdf'])
->send();// OK: Universally understood
sort($array, SORT_DESC); // Not a boolean, but similar
array_unique($array, SORT_REGULAR);
// OK: Common pattern
get_post_meta($post_id, $key, $single = false);// OK with named parameters (PHP 8.0+)
$result = search(
query: 'wordpress',
case_sensitive: true,
whole_word: false
);class PostProcessor {
public function process_published(): void {
$this->process(published_only: true);
}
public function process_all(): void {
$this->process(published_only: false);
}
private function process(bool $published_only): void {
// Private method, called only internally
// Boolean is OK here
}
}function create_user($user, $email, $send_email = true, $admin = false, $login = false) {
// 50 lines of mixed logic
}
// Called everywhere:
create_user('john', 'john@test.com', true, false, true);
create_user('admin', 'admin@test.com', false, true, false);// Keep old function for compatibility
function create_user($user, $email, $send_email = true, $admin = false, $login = false) {
return (new UserCreator($user, $email))
->with_welcome_email($send_email)
->as_admin($admin)
->with_auto_login($login)
->create();
}
// New, better API
class UserCreator {
// Builder pattern
}
// New code uses builder
$id = (new UserCreator('john', 'email@test.com'))
->with_welcome_email()
->create();/**
* @deprecated Use UserCreator instead
*/
function create_user($user, $email, $send_email = true, $admin = false, $login = false) {
_deprecated_function(__FUNCTION__, '2.0.0', 'UserCreator');
// ... implementation
}After migration period, remove old function.
✅ Use separate functions for different behaviors
✅ Use enums for predefined options (PHP 8.1+)
✅ Use builder pattern for complex object creation
✅ Use named arguments when you must have optional parameters
✅ Use options objects to group related parameters
❌ Don't use boolean flags to change function behavior
❌ Don't have multiple boolean parameters
❌ Don't make callers guess what true means
❌ Don't use booleans for mode selection
❌ Don't use booleans that return different types
If you need to look at the function definition to understand what true means, use a different approach.
// BAD: Must look up what true means
delete_post( 123, true );
// GOOD: Obvious what's happening
delete_post_permanently( 123 );
// ALSO GOOD: Enum makes it clear
delete_post( 123, DeletionMode::PERMANENT );
// ALSO GOOD: Named argument
delete_post( 123, permanent: true );Ask yourself:
- "Will someone reading this code understand what's happening without looking up the function?"
- If no → refactor away the boolean parameter!