PHP use an enum for Filters

David Carr

2 min read - 3rd May, 2024

Filter enum Testing pestphp

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.

0 comments
Add a comment

Copyright © 2006 - 2024 DC Blog - All rights reserved.