I have a class that allows filtering based on an option, I need a way to accept options and also reject invalid options.
In the past, I would reach for a switch statement, or more recently a match statement. But an enum is better suited for this task.
Using an array
public function filter($key, $value): static
{
$validOptions = [
'ids' => 'ids',
'includeArchived' => 'includeArchived',
'order' => 'order',
'page' => 'page',
'searchTerm' => 'searchTerm',
'summaryOnly' => 'summaryOnly',
'where' => 'where',
];
if (! in_array($key, $validOptions)) {
throw new InvalidArgumentException("Filter option '$key' is not valid.");
}
$this->queryString[$key] = $value;
return $this;
}
Using a switch statement:
public function filter($key, $value): static
{
switch ($key) {
case 'ids':
case 'includeArchived':
case 'order':
case 'page':
case 'searchTerm':
case 'summaryOnly':
case 'where':
$this->queryString[$key] = $value;
break;
default:
throw new InvalidArgumentException("Filter option '$key' is not valid.");
}
$this->queryString[$key] = $value;
return $this;
}
Using a match statement:
public function filter($key, $value): static
{
$this->queryString[$key] = match ($key) {
'ids', 'includeArchived', 'order', 'page', 'searchTerm', 'summaryOnly', 'where' => $this->queryString[$key] = $value,
default => throw new InvalidArgumentException("Filter option '$key' is not valid."),
};
return $this;
}
Using an enum
All the logic is stored in an enum class making the filter method smaller:
public function filter($key, $value): static
{
if (! ContactFilterOptions::isValid($key)) {
throw new InvalidArgumentException("Filter option '$key' is not valid.");
}
$this->queryString[$key] = $value;
return $this;
}
The advantage to this approach the enum is reusable, allowing to easily test the behaviour of both the enum and filter method.
The enum class looks like this:
enum ContactFilterOptions: string {
case IDS = 'ids';
case INCLUDEARCHIVED = 'includeArchived';
case ORDER = 'order';
case PAGE = 'page';
case SEARCHTERM = 'searchTerm';
case SUMMARYONLY = 'summaryOnly';
case WHERE = 'where';
public static function isValid(string $value): bool
{
$validValues = array_map(fn($case) => $case->value, self::cases());
return in_array($value, $validValues);
}
}
The isValid method is an easy way to determine if the string is in the lost of accepted values.
Tests
Adding tests to confirm for valid and invalid matches:
test('a valid option returns true', function() {
assertTrue(ContactFilterOptions::isValid('ids'));
assertTrue(ContactFilterOptions::isValid('includeArchived'));
assertTrue(ContactFilterOptions::isValid('order'));
assertTrue(ContactFilterOptions::isValid('page'));
assertTrue(ContactFilterOptions::isValid('searchTerm'));
assertTrue(ContactFilterOptions::isValid('summaryOnly'));
assertTrue(ContactFilterOptions::isValid('where'));
});
test('an invalid option returns false', function() {
assertFalse(ContactFilterOptions::isValid('bogus'));
});
Testing the filter method throws an exception:
test('invalid filter option throws exception', function(){
(new Contacts())->filter('bogus', 1);
})->throws(InvalidArgumentException::class, "Filter option 'bogus' is not valid.");
With this approach, your methods become smaller and easier to test.