Implementing custom filters

What makes a filter

A filter is a pure function [1] that takes a string and returns a string. There are three ways to implement them.

Classic function

<?php
function append_question_mark(string $text): string
{
    return $text . '?';
}

Anonymous function

<?php
$appendQuestionMark = static fn (string $text): string => $text . '?';

Callable class

<?php
use Uffff\Contract\Filter;

/**
 * @psalm-immutable
 */
readonly class AppendQuestionMark implements Filter
{
    public function __invoke(string $text): string
    {
        return $text . '?';
    }
}

The preferred version is to use a callable class but you do you.

Building a custom filter chain

To build filter chains Uffff\Builder\FilterBuilder exists. The build method returns a function that chains all configured filters and adheres to the contract of any filter, meaning it takes a single argument of type string and returns a string.

<?php
use Uffff\Builder\FilterBuilder;

$filter = (new FilterBuilder())
    ->build();

$text = $filter('some text');

To add a custom filter, call add on the builder object.

<?php
use Uffff\Builder\FilterBuilder;
use App\QuestionMarkFilter;

$filter = (new FilterBuilder())
    ->add(new QuestionMarkFilter())
    ->build();

$text = $filter('some text');

Bring your own API

Since your custom filter chain most likely needs to work in exactly the same way in many places, wrap it in a custom filter function to provide your own internal API.

<?php
namespace App;

use Uffff\Builder\FilterBuilder;
use App\QuestionMarkFilter;

function questionable_unicode(string $text): string
{
    static $filter = null;

    $filter ??= (new FilterBuilder())
        ->add(new QuestionMarkFilter())
        ->build();

    return $filter($text);
}

function questionable_unicode_or_null(?string $text)
{
    if ($text === null) {
        return null;
    }

    return questionable_unicode($text);
}